Таким образом, оказывается, что это возможно в сущности так, как я надеялся с помощью стабильного компилятора.
Если мы примем, что нам нужно работать относительно корня ящика, мы можем определить наши пути как таковые.
Полезно, что внутри кода макроса std::env::current_dir()
вернет текущий рабочий каталог в качестве корня ящика, содержащего сайт вызова. Это означает, что даже если вызов макроса находится внутри некоторой иерархии ящиков, он все равно будет возвращать путь, который имеет смысл в месте вызова макроса.
Следующий пример макроса выполняет по существу то, что мне нужно. Для краткости, он не предназначен для правильной обработки ошибок:
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");
, где mydir
- каталог в корне вызывающего ящика.
Это использует хак с использованием include_str!()
в выводе макроса, чтобы вызвать перестройку при изменениях 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());