From 72282e5239ffd9473c63566b6d5204d3a1e61267 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 4 Nov 2026 09:28:56 -0700 Subject: [PATCH] Don't embed `OUT_DIR` in proc-macros This causes problems with reproducible builds and caches and such, so instead change the meaning of `WIT_BINDGEN_DEBUG=..` to require a specific directory to be placed into. This is then used in debugging rather than the target out-dir. Closes #1777 --- README.md | 2 +- crates/guest-rust/macro/build.rs | 5 --- crates/guest-rust/macro/src/lib.rs | 69 ++++++++++++++++++++++++------ crates/guest-rust/src/lib.rs | 25 ++++++----- 4 files changed, 70 insertions(+), 31 deletions(-) delete mode 100644 crates/guest-rust/macro/build.rs diff --git a/README.md b/README.md index 01c98448d..52642d304 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ By using [`cargo expand`](https://github.com/dtolnay/cargo-expand) or `cargo doc` you can also explore the generated code. If there's a bug in `wit-bindgen` and the generated bindings do not compile or if there's an error in the generated code (which is probably also a bug in `wit-bindgen`), you can use -`WIT_BINDGEN_DEBUG=1` as an environment variable to help debug this. +`WIT_BINDGEN_DEBUG=/some/dir` as an environment variable to help debug this. This project can then be built with: diff --git a/crates/guest-rust/macro/build.rs b/crates/guest-rust/macro/build.rs deleted file mode 100644 index be0ad5bc9..000000000 --- a/crates/guest-rust/macro/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - let out_dir = std::env::var("OUT_DIR").unwrap(); - println!("cargo:rustc-env=DEBUG_OUTPUT_DIR={out_dir}"); -} diff --git a/crates/guest-rust/macro/src/lib.rs b/crates/guest-rust/macro/src/lib.rs index 416f8b388..79405b194 100644 --- a/crates/guest-rust/macro/src/lib.rs +++ b/crates/guest-rust/macro/src/lib.rs @@ -1,7 +1,7 @@ use proc_macro2::{Span, TokenStream}; use quote::ToTokens; use std::collections::HashMap; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering::Relaxed}; use syn::parse::{Error, Parse, ParseStream, Result}; use syn::punctuated::Punctuated; @@ -68,7 +68,7 @@ struct Config { resolve: Resolve, world: WorldId, files: Vec, - debug: bool, + debug: Option, } /// The source of the wit package definition @@ -88,7 +88,7 @@ impl Parse for Config { let mut features = Vec::new(); let mut sync_configured = false; let mut method_chaining_configured = false; - let mut debug = false; + let mut debug = None; if input.peek(token::Brace) { let content; @@ -188,8 +188,8 @@ impl Parse for Config { Opt::DisableCustomSectionLinkHelpers(disable) => { opts.disable_custom_section_link_helpers = disable.value(); } - Opt::Debug(enable) => { - debug = enable.value(); + Opt::Debug(dir) => { + debug = Some(dir); } Opt::Sync(val, span) => { if sync_configured { @@ -294,7 +294,41 @@ fn parse_source( } impl Config { + fn debug_output_dir(&self) -> Result> { + let dir = match (&self.debug, std::env::var_os("WIT_BINDGEN_DEBUG")) { + (Some(dir), _) => { + let value = dir.value(); + if value.is_empty() { + return Err(Error::new(dir.span(), "`debug` directory cannot be empty")); + } + PathBuf::from(value) + } + (None, Some(env)) => { + // Historically `WIT_BINDGEN_DEBUG=1` was the documented way to + // turn this on, so reject that (and `0`, which previously + // still enabled debug output) rather than silently writing + // to a directory named `1` or `0`. + if env.is_empty() || env == "1" || env == "0" { + return Err(Error::new( + Span::call_site(), + format!( + "`WIT_BINDGEN_DEBUG={}` is no longer supported; set it to \ + a directory to write generated code to instead, for example \ + `WIT_BINDGEN_DEBUG=/tmp/wit-bindgen-debug`", + env.display(), + ), + )); + } + PathBuf::from(env) + } + (None, None) => return Ok(None), + }; + let root = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + Ok(Some(root.join(dir))) + } + fn expand(mut self) -> Result { + let debug_output_dir = self.debug_output_dir()?; let mut files = Default::default(); let mut generator = self.opts.build(); generator @@ -303,23 +337,30 @@ impl Config { let (_, src) = files.iter().next().unwrap(); let mut src = std::str::from_utf8(src).unwrap().to_string(); - // If a magical `WIT_BINDGEN_DEBUG` environment variable is set then - // place a formatted version of the expanded code into a file. This file - // will then show up in rustc error messages for any codegen issues and can - // be inspected manually. - if std::env::var("WIT_BINDGEN_DEBUG").is_ok() || self.debug { + // If debugging is set then place a formatted version of the expanded + // code into a file in that directory. This file will then show up in + // rustc error messages for any codegen issues and can be inspected + // manually. + if let Some(root) = debug_output_dir { static INVOCATION: AtomicUsize = AtomicUsize::new(0); - let root = Path::new(env!("DEBUG_OUTPUT_DIR")); let world_name = &self.resolve.worlds[self.world].name; let n = INVOCATION.fetch_add(1, Relaxed); - let path = root.join(format!("{world_name}{n}.rs")); + let krate = std::env::var("CARGO_CRATE_NAME").unwrap_or_default(); + let path = root.join(format!("{krate}-{world_name}-{n}.rs")); // optimistically format the code but don't require success let contents = match fmt(&src) { Ok(formatted) => formatted, Err(_) => src.clone(), }; - std::fs::write(&path, contents.as_bytes()).unwrap(); + std::fs::create_dir_all(&root) + .and_then(|()| std::fs::write(&path, contents.as_bytes())) + .map_err(|e| { + Error::new( + Span::call_site(), + format!("failed to write debug output to `{}`: {e}", path.display()), + ) + })?; src = format!("include!({path:?});"); } @@ -456,7 +497,7 @@ enum Opt { Features(Vec), DisableCustomSectionLinkHelpers(syn::LitBool), Sync(AsyncFilterSet, Span), - Debug(syn::LitBool), + Debug(syn::LitStr), ChainableMethods(ChainableMethodFilterSet, Span), MergeStructurallyEqualTypes(syn::LitBool), } diff --git a/crates/guest-rust/src/lib.rs b/crates/guest-rust/src/lib.rs index 62bbf101a..9518c278f 100644 --- a/crates/guest-rust/src/lib.rs +++ b/crates/guest-rust/src/lib.rs @@ -618,17 +618,18 @@ extern crate std; /// inevitably bugs and issues that arise. These can range from bad error /// messages to misconfigured invocations to bugs in the macro itself. To assist /// with debugging these situations the macro recognizes an environment -/// variable: +/// variable naming a directory: /// /// ```shell -/// export WIT_BINDGEN_DEBUG=1 +/// export WIT_BINDGEN_DEBUG=/tmp/wit-bindgen-debug /// ``` /// -/// When set the macro will emit the result of expansion to a file and then -/// `include!` that file. Any error messages generated by `rustc` should then -/// point to the generated file and allow you to open it up, read it, and -/// inspect it. This can often provide better context to the error than rustc -/// provides by default with macros. +/// When set the macro will emit the result of expansion to a file in that +/// directory and then `include!` that file. Relative paths are resolved +/// against the `CARGO_MANIFEST_DIR` of the crate invoking the macro. Any error +/// messages generated by `rustc` should then point to the generated file and +/// allow you to open it up, read it, and inspect it. This can often provide +/// better context to the error than rustc provides by default with macros. /// /// It is not recommended to set this environment variable by default as it will /// cause excessive rebuilds of Cargo projects. It's recommended to only use it @@ -855,10 +856,12 @@ extern crate std; /// // emit more useful diagnostics for errors in the generated code. This /// // is primarily useful for `wit-bindgen` developers. /// // -/// // This does the same thing as setting `WIT_BINDGEN_DEBUG=1`, except -/// // that it can be used on a more fine-grained basis (i.e. it only affects -/// // the specific `generate!` call where it is used. -/// debug: true, +/// // This does the same thing as setting `WIT_BINDGEN_DEBUG=`, +/// // except that it can be used on a more fine-grained basis (i.e. it only +/// // affects the specific `generate!` call where it is used). The path is +/// // relative to `CARGO_MANIFEST_DIR`, and this option takes precedence +/// // over `WIT_BINDGEN_DEBUG`. +/// debug: "target/wit-bindgen-debug", /// /// // Generate sync bindings for functions that are `async` in WIT. /// //