所以事实证明,这基本上是可能的,就像我希望使用稳定的编译器一样。
如果我们接受我们需要相对于 crate 根工作,我们可以这样定义我们的路径。
有用的是,在宏代码中,std::env::current_dir()
将返回当前工作目录作为包含调用站点的 crate 的根目录。这意味着,即使宏调用在某个 crate 层次结构中,它仍然会返回在宏调用位置有意义的路径。
下面的示例宏基本上可以满足我的需要。为简洁起见,它并非旨在正确处理错误:
extern crate proc_macro;
use quote::quote;
use proc_macro::TokenStream;
use syn::parse::{Parse, ParseStream, Result};
use syn;
use std;
use std::fs::File;
use std::io::Read;
#[derive(Debug)]
struct FileName {
filename: String,
}
impl Parse for FileName {
fn parse(input: ParseStream) -> Result<Self> {
let lit_file: syn::LitStr = input.parse()?;
Ok(Self { filename: lit_file.value() })
}
}
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as FileName);
let cwd = std::env::current_dir().unwrap();
let file_path = cwd.join(&input.filename);
let file_path_str = format!("{}", file_path.display());
println!("path: {}", file_path.display());
let mut file = File::open(file_path).unwrap();
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
println!("contents: {:?}", contents);
let result = quote!(
const FILE_STR: &'static str = include_str!(#file_path_str);
pub fn foo() -> bool {
println!("Hello");
true
}
);
TokenStream::from(result)
}
可以调用哪个
my_macro!("mydir/myfile");
wheremydir
是调用 crate 根目录中的目录。
这使用include_str!()
在宏输出中使用 hack 来导致对myfile
. 这是必要的,并且符合预期。如果它从未实际使用过,我希望它会被优化。
我很想知道这种方法是否在任何情况下都会失败。
与我最初的问题相关,当前每晚source_file()
在Span
. 这可能是实现上述内容的更好方法,但我宁愿坚持使用稳定。跟踪问题在这里。
编辑:当包在工作空间中时,上述实现失败,此时当前工作目录是工作空间根目录,而不是板条箱根目录。这很容易解决,如下所示(插入cwd
和file_path
声明之间)。
let mut cwd = std::env::current_dir().unwrap();
let cargo_path = cwd.join("Cargo.toml");
let mut cargo_file = File::open(cargo_path).unwrap();
let mut cargo_contents = String::new();
cargo_file.read_to_string(&mut cargo_contents).unwrap();
// Use a simple regex to detect the suitable tag in the toml file. Much
// simpler than using the toml crate and probably good enough according to
// the workspace RFC.
let cargo_re = regex::Regex::new(r"(?m)^\[workspace\][ \t]*$").unwrap();
let workspace_path = match cargo_re.find(&cargo_contents) {
Some(val) => std::env::var("CARGO_PKG_NAME"),
None => "".to_string()
};
let file_path = cwd.join(workspace_path).join(input.filename);
let file_path_str = format!("{}", file_path.display());