blob: eb13d7ad945b9826bc90d2288bf3e55e4b27a53d [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use proc_macro2::{Span, TokenStream};
use quote::quote;
use std::path::Path;
use std::{env, fs};
use syn::{Error, LitStr, Result};
/// Imports a file's content as a string like [`include_str!`] does but looks up files relative to
/// where rustc is run from. Accepts a relative file path.
///
/// # Example
/// ```ignore
/// // Project root: If rustc is run from `/path/to/project`, and the file lives at
/// // `/path/to/project/out/gen/file.json`, then it will be included.
/// const JSON1: &str = include_str_from_working_dir_path!("out/gen/file.json");
///
/// // Build root: If rustc is run from `/path/to/project/out/build`, and the file lives at
/// // `/path/to/project/out/build/gen/file.json`, then it will be included.
/// const JSON2: &str = include_str_from_working_dir_path!("gen/file.json");
/// ```
#[proc_macro]
pub fn include_str_from_working_dir_path(
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
include_str_from_working_dir_path_impl(input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
/// Imports a file's content as a string like [`include_str!`] does but looks up files relative to
/// where rustc is run from. Accepts an environment variable name.
///
/// # Example
/// ```ignore
/// // Project root: If rustc is run from `/path/to/project`, and the environment variable
/// // `JSON_PATH` contains the string "out/build/gen/file.json", then the file at
/// // `/path/to/project/out/build/gen/file.json` will be included.
/// // Note that only the env macro is supported.
/// const JSON1: &str = include_str_from_working_dir_env!("JSON_PATH");
///
/// // Build root: If rustc is run from `/path/to/project/out/build`, and the environment variable
/// // `JSON_PATH` contains the string "gen/file.json", then the file at
/// // `/path/to/project/out/build/gen/file.json` will be included.
/// const JSON2: &str = include_str_from_working_dir_env!("JSON_PATH");
/// ```
#[proc_macro]
pub fn include_str_from_working_dir_env(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
include_str_from_working_dir_env_impl(input.into())
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
fn read_file(path: &str) -> Result<TokenStream> {
let build_dir = env::var("FUCHSIA_BUILD_DIR").unwrap_or(".".to_string());
let contents = fs::read_to_string(Path::new(&build_dir).join(path)).map_err(|err| {
Error::new(Span::call_site(), format!("Unable to read file {path:?}: {err:?}"))
})?;
Ok(quote! { #contents })
}
fn include_str_from_working_dir_path_impl(input: TokenStream) -> Result<TokenStream> {
let lit_str: LitStr = syn::parse2(input)?;
let path = lit_str.value();
let contents = read_file(&path)?;
Ok(quote! { #contents })
}
fn include_str_from_working_dir_env_impl(input: TokenStream) -> Result<TokenStream> {
let env_var: LitStr = syn::parse2(input)?;
let path = match env::var(env_var.value()) {
Ok(path) => path,
Err(err) => {
return Err(Error::new(
env_var.span(),
format!("Invalid env var {:?}: {err:?}", env_var.value()),
));
}
};
let contents = read_file(&path)?;
Ok(quote! { #contents })
}