tracers_codegen/
build_rs.rs

1//! This module contains the code that is used within `tracers`s build.rs` file to select
2//! the suitable tracing implementation at build time, and within a dependent crate's `build.rs`
3//! file to perform the build-time code generation to support the selected tracing implementation
4
5use crate::cargo;
6use crate::error::{TracersError, TracersResult};
7use crate::gen;
8use crate::gen::NativeLib;
9use crate::TracingImplementation;
10use failure::ResultExt;
11use serde::{Deserialize, Serialize};
12use std::env;
13use std::fs::File;
14use std::io::Write;
15use std::io::{BufReader, BufWriter};
16use std::path::{Path, PathBuf};
17
18/// Captures the features enabled for the build.  There are various combinations of them which
19/// influence the logic related to what implementation is preferred
20#[derive(Debug, Clone)]
21struct FeatureFlags {
22    enable_dynamic_tracing: bool,
23    enable_static_tracing: bool,
24    force_dyn_stap: bool,
25    force_dyn_noop: bool,
26    force_static_stap: bool,
27    force_static_lttng: bool,
28    force_static_noop: bool,
29}
30
31impl FeatureFlags {
32    /// Read the feature flags from the environment variables set by Cargo at build time.
33    ///
34    /// Fails with an error if the combination of features is not valid
35    pub fn from_env() -> TracersResult<FeatureFlags> {
36        Self::new(
37            Self::is_feature_enabled("dynamic-tracing"),
38            Self::is_feature_enabled("static-tracing"),
39            Self::is_feature_enabled("force-dyn-stap"),
40            Self::is_feature_enabled("force-dyn-noop"),
41            Self::is_feature_enabled("force-static-stap"),
42            Self::is_feature_enabled("force-static-lttng"),
43            Self::is_feature_enabled("force-static-noop"),
44        )
45    }
46
47    /// Creates a feature flag structure from explicit arguments.  Mostly used for testing
48    pub fn new(
49        enable_dynamic_tracing: bool,
50        enable_static_tracing: bool,
51        force_dyn_stap: bool,
52        force_dyn_noop: bool,
53        force_static_stap: bool,
54        force_static_lttng: bool,
55        force_static_noop: bool,
56    ) -> TracersResult<FeatureFlags> {
57        if enable_dynamic_tracing && enable_static_tracing {
58            return Err(TracersError::code_generation_error("The features `dynamic-tracing` and `static-tracing` are mutually exclusive; please choose one"));
59        }
60
61        if force_dyn_stap && force_dyn_noop {
62            return Err(TracersError::code_generation_error("The features `force-dyn-stap` and `force_dyn_noop` are mutually exclusive; please choose one"));
63        }
64
65        if force_static_stap && force_static_noop {
66            return Err(TracersError::code_generation_error("The features `force-static-stap` and `force_static_noop` are mutually exclusive; please choose one"));
67        }
68
69        if force_static_lttng && force_static_noop {
70            return Err(TracersError::code_generation_error("The features `force-static-lttng` and `force_static_noop` are mutually exclusive; please choose one"));
71        }
72
73        Ok(FeatureFlags {
74            enable_dynamic_tracing,
75            enable_static_tracing,
76            force_dyn_stap,
77            force_dyn_noop,
78            force_static_stap,
79            force_static_lttng,
80            force_static_noop,
81        })
82    }
83
84    pub fn enable_tracing(&self) -> bool {
85        self.enable_dynamic() || self.enable_static()
86    }
87
88    pub fn enable_dynamic(&self) -> bool {
89        self.enable_dynamic_tracing || self.force_dyn_noop || self.force_dyn_stap
90    }
91
92    pub fn enable_static(&self) -> bool {
93        self.enable_static_tracing
94    }
95
96    pub fn force_dyn_stap(&self) -> bool {
97        //Should the dynamic stap be required on pain of build failure?
98        self.force_dyn_stap
99    }
100
101    pub fn force_dyn_noop(&self) -> bool {
102        //Should the dynamic stap be required on pain of build failure?
103        self.force_dyn_noop
104    }
105
106    pub fn force_static_stap(&self) -> bool {
107        //Should the staticamic stap be required on pain of build failure?
108        self.force_static_stap
109    }
110
111    pub fn force_static_lttng(&self) -> bool {
112        //Should the static lttng be required on pain of build failure?
113        self.force_static_lttng
114    }
115
116    fn is_feature_enabled(name: &str) -> bool {
117        env::var(&format!(
118            "CARGO_FEATURE_{}",
119            name.to_uppercase().replace("-", "_")
120        ))
121        .is_ok()
122    }
123}
124
125/// Serializable struct which is populated in `build.rs` to indicate to the proc macros which
126/// tracing implementation they should use.
127#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
128pub(crate) struct BuildInfo {
129    pub package_name: String,
130    pub implementation: TracingImplementation,
131}
132
133impl BuildInfo {
134    pub fn new(package_name: String, implementation: TracingImplementation) -> BuildInfo {
135        BuildInfo {
136            package_name,
137            implementation,
138        }
139    }
140
141    pub fn load() -> TracersResult<BuildInfo> {
142        let path = Self::get_build_path()?;
143
144        let file = File::open(&path)
145            .map_err(|e| TracersError::build_info_read_error(path.clone(), e.into()))?;
146        let reader = BufReader::new(file);
147
148        serde_json::from_reader(reader)
149            .map_err(|e| TracersError::build_info_read_error(path.clone(), e.into()))
150    }
151
152    pub fn save(&self) -> TracersResult<PathBuf> {
153        let path = Self::get_build_path()?;
154
155        //Make sure the directory exists
156        path.parent()
157            .map(|p| {
158                std::fs::create_dir_all(p)
159                    .map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))
160            })
161            .unwrap_or(Ok(()))?;
162
163        let file = File::create(&path)
164            .map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))?;
165        let writer = BufWriter::new(file);
166        serde_json::to_writer(writer, self)
167            .map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))?;
168
169        Ok(path)
170    }
171
172    fn get_build_path() -> TracersResult<PathBuf> {
173        //HACK: This is...not the most elegant solution.  This code gets used in three contexts:
174        //
175        //1. When `tracers` itself is being built, its `build.rs` calls into `tracers-build` to
176        //   decide which probing implementation to use based on the feature flags specified by the
177        //   caller.  In that case, `OUT_DIR` is set by `cargo` to the out directory for the
178        //   `tracers` crate.  In that situation the `BuildInfo` file should go somewhere in
179        //   `$OUT_DIR`, in a subdirectory named with the `tracers` package name and version
180        //
181        //2. When some other crate is using `tracers`, its `build.rs` calls into `tracers-build` to
182        //   perform the build-time code generation tasks, which first require knowing which
183        //   implementation `tracers` is using, hence requires reading the `BuildInfo` file.  In
184        //   this case, `$OUT_DIR` is set to that dependent crate's output directory and is not
185        //   what we want.  We want to know the path to the `BuildInfo` file produced when
186        //   `tracers` was built.  Fortunately this information is passed on to the dependent crate
187        //   by `tracers`s `build.rs` in the form of a cargo variable which cargo propagates to
188        //   dependent crates as `DEP_TRACERS_BUILD_INFO_PATH`
189        //
190        //3. When one of the proc macros is invoked during compilation of some crate which is using
191        //   `tracers`.  In this case, the other crate's `build.rs` should have already been run,
192        //   and it should have called into the `tracers_build::build()` (that call is what creates
193        //   context #2 above).  `tracers_build::build()` will instruct Cargo to set an environment
194        //   variable `TRACERS_BUILD_INFO_PATH` during compilation, which this method will then
195        //   example in order to get the path of the `BuidlInfo` build.
196        //
197        //I'm not proud of this code.  But `tracers` pushes the Rust build system just about to the
198        //breaking point as it is.  We're lucky it's possible at all, hacks notwithstanding
199        if "tracers" == env::var("CARGO_PKG_NAME").ok().unwrap_or_default() {
200            //This is context #1 in the comment above: we're being called from within the `tracers`
201            //build.rs
202            let rel_path = PathBuf::from(&format!(
203                "{}-{}/buildinfo.json",
204                env::var("CARGO_PKG_NAME").context("CARGO_PKG_NAME")?,
205                env::var("CARGO_PKG_VERSION").context("CARGO_PKG_VERSION")?
206            ));
207
208            Ok(PathBuf::from(env::var("OUT_DIR").context("OUT_DIR")?).join(rel_path))
209        } else if let Ok(build_info_path) = env::var("DEP_TRACERS_BUILD_INFO_PATH") {
210            //This is context #2 in the comment above
211            Ok(PathBuf::from(build_info_path))
212        } else if let Ok(build_info_path) = env::var("TRACERS_BUILD_INFO_PATH") {
213            //This is context #3 in the comment above
214            Ok(PathBuf::from(build_info_path))
215        } else {
216            //Since the first context happens in the `tracers` code itself and we know it's
217            //implemented correctly, it means that this is either context #2 or #3 and the caller
218            //did something wrong.  Most likely they forgot to add the call to
219            //`tracers_build::build()` to their `build.rs`.  Since this is an easy mistake to make
220            //we want an ergonomic error message here
221            Err(TracersError::missing_call_in_build_rs())
222        }
223    }
224}
225
226/// Called from the `build.rs` of all crates which have a direct dependency on `tracers` and
227/// `tracers_macros`.  This determines the compile-time configuration of the `tracers` crate, and
228/// performs any build-time code generation necessary to support the code generated by the
229/// `tracers_macros` macros.
230///
231/// It should be the first line in the `main()` function, etc:
232///
233/// ```no_execute
234/// // build.rs
235/// use tracers_build::build;
236///
237/// fn main() {
238///     build();
239///
240///     //....
241/// }
242/// ```
243pub fn build() {
244    let mut stdout = std::io::stdout();
245    let mut stderr = std::io::stderr();
246
247    match build_internal(&mut stdout) {
248        Ok(_) => writeln!(stdout, "probes build succeeded").unwrap(),
249        Err(e) => {
250            //An error that propagates all the way up to here is serious enough that it means we
251            //cannot proceed.  Fail the build by exiting the process forcefully
252            writeln!(stderr, "Error building probes: {}", e).unwrap();
253
254            std::process::exit(-1);
255        }
256    };
257}
258
259fn build_internal<OUT: Write>(out: &mut OUT) -> TracersResult<()> {
260    //First things first; get the BuildInfo from the `tracers` build, and tell Cargo to make that
261    //available to the proc macros at compile time via an environment variable
262    let build_info_path = BuildInfo::get_build_path()?;
263    writeln!(
264        out,
265        "cargo:rustc-env=TRACERS_BUILD_INFO_PATH={}",
266        build_info_path.display()
267    )
268    .unwrap();
269
270    generate_native_code(out)
271}
272
273/// This function is the counterpart to `build`, which is intended to be invoked in the `tracers`
274/// `build.rs` script.  It reads the feature flags enabled on `tracers`, and from those flags and
275/// other information about the target sytem and the local build environment selects an
276/// implementation to use, or panics if no suitable implementation is possible
277pub fn tracers_build() {
278    let mut stdout = std::io::stdout();
279    let mut stderr = std::io::stderr();
280
281    let features = FeatureFlags::from_env().expect("Invalid feature flags");
282
283    match tracers_build_internal(&mut stdout, features) {
284        Ok(_) => {}
285        Err(e) => {
286            //failure here doesn't just mean one of the tracing impls failed to compile; when that
287            //happens we can always fall back to the no-op impl.  This means something happened
288            //which prevents us from proceeding with the build
289            writeln!(stderr, "{}", e).unwrap();
290            panic!("tracers build failed: {}", e);
291        }
292    }
293}
294
295fn tracers_build_internal<OUT: Write>(out: &mut OUT, features: FeatureFlags) -> TracersResult<()> {
296    writeln!(out, "Detected features: \n{:?}", features).unwrap();
297
298    select_implementation(&features).map(|implementation| {
299            // Some implementation was selected, but it's possible that the selected
300            // "implementation" is to completely disable tracing.  If that's not the case, set the
301            // appropriate features for the compiler to use when compiling the `tracers` code.
302            if implementation.is_enabled() {
303                writeln!(out, "cargo:rustc-cfg=enabled").unwrap();
304                writeln!(out,
305                    "cargo:rustc-cfg={}_enabled",
306                    if implementation.is_static() {
307                        "static"
308                    } else {
309                        "dynamic"
310                    }
311                ).unwrap(); //this category of tracing is enabled
312                writeln!(out, "cargo:rustc-cfg={}_enabled", implementation.as_ref()).unwrap(); //this specific impl is enabled
313            }
314
315            //All downstream creates from `tracers` will just call `tracers_build::build`, but this
316            //is a special case because we've already decided above which implementation to use.
317            //
318            //This decision needs to be saved to the OUT_DIR somewhere, so that all of our tests,
319            //examples, binaries, and benchmarks which use the proc macros will be able to generate
320            //the correct runtime tracing code to match the implementation we've chosen here
321            let build_info = BuildInfo::new(env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME"), implementation);
322            match build_info.save() {
323                Ok(build_info_path) => {
324                    //The above statements set compile-time features to the compiler knows which modules to
325                    //include.  The below will set environment variables DEP_TRACERS_(VARNAME) in dependent
326                    //builds
327                    //
328                    //The codegen stuff in `tracers_build::build` will use this to determine what code
329                    //generator to use
330                    writeln!(out, "cargo:build-info-path={}", build_info_path.display()).unwrap();
331                }
332                Err(e) => {
333                    writeln!(out, "cargo:warning=Error saving build info file; some targets may fail to build.  Error details: {}", e).unwrap();
334                }
335            }
336    })?;
337
338    //Generate native code for the `tracers` crate.  Nothing in the actual `tracers`
339    //library code contains any `#[tracer]` traits, but the tests and examples do, so if we
340    //want them to work propertly we need to run codegen for them just like on any other
341    //crate
342    generate_native_code(out)
343}
344
345/// Selects a `tracers` implementation given a set of feature flags specified by the user
346fn select_implementation(features: &FeatureFlags) -> TracersResult<TracingImplementation> {
347    if !features.enable_tracing() {
348        return Ok(TracingImplementation::Disabled);
349    }
350
351    //If any implementation is forced, then see if it's available and if so then accept it
352    if features.enable_dynamic() {
353        // Pick some dynamic tracing impl
354        if features.force_dyn_stap() {
355            if env::var("DEP_TRACERS_DYN_STAP_SUCCEEDED").is_err() {
356                return Err(TracersError::code_generation_error(
357                    "force-dyn-stap is enabled but the dyn_stap library is not available",
358                ));
359            } else {
360                return Ok(TracingImplementation::DynamicStap);
361            }
362        } else if features.force_dyn_noop() {
363            //no-op is always available on all platforms
364            return Ok(TracingImplementation::DynamicNoOp);
365        }
366
367        //Else no tracing impl has been forced so we get to decide
368        if env::var("DEP_TRACERS_DYN_STAP_SUCCEEDED").is_ok() {
369            //use dyn_stap when it savailable
370            Ok(TracingImplementation::DynamicStap)
371        } else {
372            //else, fall back to noop
373            Ok(TracingImplementation::DynamicNoOp)
374        }
375    } else {
376        // Pick some static tracing impl
377        assert!(features.enable_static());
378
379        //TODO: Be a bit smarter about this
380        if features.force_static_stap() {
381            Ok(TracingImplementation::StaticStap)
382        } else if features.force_static_lttng() {
383            Ok(TracingImplementation::StaticLttng)
384        } else {
385            Ok(TracingImplementation::StaticNoOp)
386        }
387    }
388}
389
390fn generate_native_code(out: &mut dyn Write) -> TracersResult<()> {
391    let manifest_dir = env::var("CARGO_MANIFEST_DIR").context(
392        "CARGO_MANIFEST_DIR is not set; are you sure you're calling this from within build.rs?",
393    )?;
394
395    let manifest_path = PathBuf::from(manifest_dir).join("Cargo.toml");
396    let package_name = env::var("CARGO_PKG_NAME").unwrap();
397    let targets = cargo::get_targets(&manifest_path, &package_name).context("get_targets")?;
398    let out_path = &PathBuf::from(env::var("OUT_DIR").context("OUT_DIR")?);
399
400    let mut native_libs = gen::code_generator()?.generate_native_code(
401        out,
402        &Path::new(&manifest_path),
403        &out_path,
404        &package_name,
405        targets,
406    );
407
408    //There is usually some repetition when multiple providers are generated.  Filter that out for
409    //better build performance
410    native_libs.sort_by(|a, b| a.partial_cmp(b).unwrap());
411    native_libs.dedup();
412
413    //Scan through all of the native libs output and send the info to cargo as applicable
414    for native_lib in native_libs.into_iter() {
415        match native_lib {
416            NativeLib::StaticWrapperLib(_) => {
417                //This is the name of a generated native wrapper.  Ignore it here; the `tracers`
418                //attribute macro will use this to generate a `link` attribute at the actual call
419                //site
420            }
421            NativeLib::StaticWrapperLibPath(path) | NativeLib::SupportLibPath(path) => {
422                //This is the path to a directory where either the static wrapper or a support lib
423                //will be found.  Make sure cargo adds that to the library path
424                println!("cargo:rustc-link-search=native={}", path.display());
425            }
426            NativeLib::DynamicSupportLib(lib) => {
427                //This is a dynamically-linked support library which should be linked exactly once
428                //for the crate, to support any number of generated native wrappers
429                println!("cargo:rustc-link-lib=dylib={}", lib);
430            }
431            NativeLib::StaticSupportLib(lib) => {
432                //This is a statically-linked support library which should be linked exactly once
433                //for the crate, to support any number of generated native wrappers
434                println!("cargo:rustc-link-lib=static={}", lib);
435            }
436        };
437    }
438
439    Ok(())
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445    use crate::testdata;
446    use crate::TracingType;
447
448    #[test]
449    #[should_panic]
450    fn tracers_build_panics_invalid_features() {
451        //These two feature flags are mutually exclusive
452        let guard = testdata::with_env_vars(vec![
453            ("CARGO_FEATURE_STATIC_TRACING", "1"),
454            ("CARGO_FEATURE_DYNAMIC_TRACING", "1"),
455        ]);
456
457        tracers_build();
458
459        drop(guard);
460    }
461
462    #[test]
463    fn build_rs_workflow_tests() {
464        // Simulates the entire process, starting with `tracers_build` choosing an implementation
465        // based on the selected feature flags, then the dependent crate calling `build` to query
466        // the build info generated by `tracers_build` and to perform  perform
467        // pre-processing of its code, then the proc macros reading the build info persisted by
468        // `build` to generate the right implementation.
469        //
470        // This doesn't actually integrate all those systems in a test, but it simulates the
471        // relevant calls into the `build_rs` code
472        let test_cases = vec![
473            //features, expected_impl
474            (
475                // Tracing disabled entirely
476                FeatureFlags::new(false, false, false, false, false, false, false).unwrap(),
477                TracingImplementation::Disabled,
478            ),
479            (
480                // Tracing enabled, dynamic mode enabled with auto-detect, static disabled
481                FeatureFlags::new(true, false, false, false, false, false, false).unwrap(),
482                TracingImplementation::DynamicNoOp,
483            ),
484            (
485                // Tracing enabled, dynamic disabled, static enabled with auto-detect
486                FeatureFlags::new(false, true, false, false, false, false, false).unwrap(),
487                TracingImplementation::StaticNoOp,
488            ),
489            (
490                // Tracing enabled, dynamic disabled, static enabled with force-static-noop
491                FeatureFlags::new(false, true, false, false, false, false, true).unwrap(),
492                TracingImplementation::StaticNoOp,
493            ),
494            (
495                // Tracing enabled, dynamic disabled, static enabled with force-static-stap
496                FeatureFlags::new(false, true, false, false, true, false, false).unwrap(),
497                TracingImplementation::StaticStap,
498            ),
499            (
500                // Tracing enabled, dynamic disabled, static enabled with force-static-lttng
501                FeatureFlags::new(false, true, false, false, false, true, false).unwrap(),
502                TracingImplementation::StaticLttng,
503            ),
504        ];
505
506        let temp_dir = tempfile::tempdir().unwrap();
507        let manifest_dir = env!("CARGO_MANIFEST_DIR");
508        let out_dir = temp_dir.path().join("out");
509
510        for (features, expected_impl) in test_cases.into_iter() {
511            let context = format!(
512                "features: {:?}\nexpected_impl: {}",
513                features,
514                expected_impl.as_ref()
515            );
516
517            //First let's pretend we're in `tracers/build.rs`, and cargo has set the relevant env
518            //vars
519            let guard = testdata::with_env_vars(vec![
520                ("CARGO_PKG_NAME", "tracers"),
521                ("CARGO_PKG_VERSION", "1.2.3"),
522                ("CARGO_MANIFEST_DIR", manifest_dir),
523                ("OUT_DIR", out_dir.to_str().unwrap()),
524            ]);
525
526            let mut stdout = Vec::new();
527
528            tracers_build_internal(&mut stdout, features.clone())
529                .expect(&format!("Unexpected failure with features: {:?}", features));
530
531            //That worked.  The resulting build info should have been written out
532            let build_info_path = BuildInfo::get_build_path().unwrap();
533
534            let step1_build_info = BuildInfo::load().expect(&format!(
535                "Failed to load build info for features: {:?}",
536                features
537            ));
538
539            assert_eq!(
540                expected_impl, step1_build_info.implementation,
541                "context: {}",
542                context
543            );
544
545            //And the path to this should have been written to stdout such that cargo will treat it
546            //as a variable that is passed to dependent crates' `build.rs`:
547            let output = String::from_utf8(stdout).unwrap();
548            assert!(
549                output.contains(&format!(
550                    "cargo:build-info-path={}",
551                    build_info_path.display()
552                )),
553                context
554            );
555
556            //and the features used to compile `tracers` should correspond to the implementation
557            match expected_impl.tracing_type() {
558                TracingType::Disabled => assert!(!output.contains("enabled")),
559                TracingType::Dynamic => assert!(output.contains("cargo:rustc-cfg=dynamic_enabled")),
560                TracingType::Static => assert!(output.contains("cargo:rustc-cfg=static_enabled")),
561            }
562
563            if expected_impl.is_enabled() {
564                assert!(
565                    output.contains(&format!(
566                        "cargo:rustc-cfg={}_enabled",
567                        expected_impl.as_ref()
568                    )),
569                    context
570                );
571            }
572
573            //Next, the user crate's `build.rs` will want to know what the selected impl was
574            drop(guard);
575            let guard = testdata::with_env_vars(vec![
576                (
577                    "DEP_TRACERS_BUILD_INFO_PATH",
578                    build_info_path.to_str().unwrap(),
579                ),
580                ("OUT_DIR", out_dir.to_str().unwrap()),
581            ]);
582
583            let step2_build_info = BuildInfo::load().expect(&format!(
584                "Failed to load build info for features: {:?}",
585                features
586            ));
587
588            assert_eq!(step1_build_info, step2_build_info, "context: {}", context);
589            drop(guard);
590
591            //At this point in the process if this were a real build, the `build.rs` code would be
592            //generating code for a real crate.  We're not going to simulate all of that here,
593            //however we can invoke the code gen for all of our test crates at this point, and the
594            //code gen should work using the currently selected implementatoin
595            for test_case in testdata::TEST_CRATES.iter() {
596                let context = format!(
597                    "features: {:?} test_case: {}",
598                    features,
599                    test_case.root_directory.display()
600                );
601
602                let mut stdout = Vec::new();
603
604                let guard = testdata::with_env_vars(vec![
605                    (
606                        "DEP_TRACERS_BUILD_INFO_PATH",
607                        build_info_path.to_str().unwrap(),
608                    ),
609                    ("OUT_DIR", out_dir.to_str().unwrap()),
610                    ("CARGO_PKG_NAME", test_case.package_name),
611                    (
612                        "CARGO_MANIFEST_DIR",
613                        test_case.root_directory.to_str().unwrap(),
614                    ),
615                    ("TARGET", "x86_64-linux-gnu"),
616                    ("HOST", "x86_64-linux-gnu"),
617                    ("OPT_LEVEL", "1"),
618                ]);
619
620                build_internal(&mut stdout).expect(&context);
621
622                //After the build, it should output something on stdout to tell Cargo to set a
623                //compiler-visible env var telling the proc macros where the `BuildInfo` file is
624                let output = String::from_utf8(stdout).unwrap();
625                assert!(output.contains(&format!(
626                    "cargo:rustc-env=TRACERS_BUILD_INFO_PATH={}",
627                    build_info_path.display()
628                )));
629
630                drop(guard);
631            }
632
633            //That worked, next the proc macros will be run by `rustc` while it builds the user
634            //crate.
635            let guard = testdata::with_env_vars(vec![(
636                "TRACERS_BUILD_INFO_PATH",
637                build_info_path.to_str().unwrap(),
638            )]);
639
640            let step3_build_info = BuildInfo::load().expect(&format!(
641                "Failed to load build info for features: {:?}",
642                features
643            ));
644
645            assert_eq!(step1_build_info, step3_build_info, "context: {}", context);
646
647            drop(guard);
648        }
649    }
650}