-
Notifications
You must be signed in to change notification settings - Fork 1.1k
ctest: Add translation of Rust types. #4501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[[escaper]] | ||
path = "askama::filters::Text" | ||
extensions = ["rs", "c", "cpp"] | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
use std::env; | ||
|
||
// When we call `cargo test` for a cross compiled target, the following is required: | ||
// - CARGO_TARGET_{}_LINKER: To link the integration tests. | ||
// - CARGO_TARGET_{}_RUNNER: To run the integration tests. | ||
// | ||
// This is already set by the CI for all platforms, so there is no problem up till here. | ||
// | ||
// The integration tests (which are run in qemu, but use host rustc and cc) require the | ||
// following: | ||
// - TARGET_PLATFORM or target set manually. (We forward TARGET in build.rs for this.) | ||
// - HOST_PLATFORM or host set manually. (We forward HOST in build.rs for this.) | ||
// - LINKER: To link the C headers. (We forward CARGO_TARGET_{}_LINKER for this.) | ||
// - FLAGS: Any flags to pass when compiling the test binary for the cross compiled platform. | ||
// (Forwarded from CARGO_TARGET_{}_RUSTFLAGS) | ||
// - RUNNER: To run the test binary with. (Forward the same runner as CARGO_TARGET_{}_RUNNER) | ||
// | ||
// The TARGET_PLATFORM and HOST_PLATFORM variables are not an issue, cargo will automatically set | ||
// TARGET and PLATFORM and we will forward them. | ||
// | ||
// Similarly FLAGS and RUNNER are also not an issue, if CARGO_TARGET_{}_RUSTFLAGS are present | ||
// they're forwarded. And RUSTFLAGS works by default anyway. Similarly the test binary doesn't | ||
// require any external applications so just the RUNNER is enough to run it. | ||
// | ||
// However since rustc and cc are the host versions, they will only work if we specify the | ||
// correct variables for them. Because we only use them to compile, not run things. For CC we | ||
// MUST specify CC or CC_target otherwise it will fail. (Other flags like AR etc. work without | ||
// forwarding because it is run in the host.) For rustc we MUST specify the correct linker. | ||
// Usually this is the same as CC or CC_target. | ||
// | ||
// In the CI, the CARGO_TARGET_{} variables are always set. | ||
|
||
fn main() { | ||
let host = env::var("HOST").unwrap(); | ||
let target = env::var("TARGET").unwrap(); | ||
let target_key = target.replace('-', "_").to_uppercase(); | ||
|
||
println!("cargo:rustc-env=HOST_PLATFORM={host}"); | ||
println!("cargo:rerun-if-changed-env=HOST"); | ||
|
||
println!("cargo:rustc-env=TARGET_PLATFORM={target}"); | ||
println!("cargo:rerun-if-changed-env=TARGET"); | ||
|
||
let link_var = format!("CARGO_TARGET_{target_key}_LINKER"); | ||
println!("cargo:rerun-if-changed-env={link_var}"); | ||
if let Ok(linker) = env::var(link_var) { | ||
println!("cargo:rustc-env=LINKER={linker}"); | ||
} | ||
|
||
let run_var = format!("CARGO_TARGET_{target_key}_RUNNER"); | ||
println!("cargo:rerun-if-changed-env={run_var}"); | ||
if let Ok(runner) = env::var(run_var) { | ||
println!("cargo:rustc-env=RUNNER={runner}"); | ||
} | ||
|
||
// As we invoke rustc directly this does not get passed to it, although RUSTFLAGS does. | ||
let flag_var = format!("CARGO_TARGET_{target_key}_RUSTFLAGS"); | ||
println!("cargo:rerun-if-changed-env={flag_var}"); | ||
if let Ok(flags) = env::var(flag_var) { | ||
println!("cargo:rustc-env=FLAGS={flags}"); | ||
} | ||
|
||
// Rerun this build script if any of these environment variables change. | ||
println!("cargo:rerun-if-changed-env=CC"); | ||
println!( | ||
"cargo:rerun-if-changed-env=CC_{}", | ||
target_key.to_lowercase() | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,28 +1,131 @@ | ||
use std::path::Path; | ||
use std::{ | ||
env, | ||
fs::File, | ||
io::Write, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use askama::Template; | ||
use syn::visit::Visit; | ||
use thiserror::Error; | ||
|
||
use crate::{expand, ffi_items::FfiItems, Result}; | ||
use crate::{ | ||
expand, | ||
ffi_items::FfiItems, | ||
template::{CTestTemplate, RustTestTemplate}, | ||
}; | ||
|
||
#[derive(Debug, Error)] | ||
pub enum GenerationError { | ||
#[error("unable to expand crate {0}: {1}")] | ||
MacroExpansion(PathBuf, String), | ||
#[error("unable to parse expanded crate {0}: {1}")] | ||
RustSyntax(String, String), | ||
#[error("unable to render {0} template: {1}")] | ||
TemplateRender(String, String), | ||
#[error("unable to create or write template file: {0}")] | ||
OsError(std::io::Error), | ||
} | ||
|
||
/// A builder used to generate a test suite. | ||
#[non_exhaustive] | ||
#[derive(Default, Debug, Clone)] | ||
pub struct TestGenerator {} | ||
pub struct TestGenerator { | ||
headers: Vec<String>, | ||
pub(crate) target: Option<String>, | ||
pub(crate) includes: Vec<PathBuf>, | ||
out_dir: Option<PathBuf>, | ||
} | ||
|
||
impl TestGenerator { | ||
/// Creates a new blank test generator. | ||
pub fn new() -> Self { | ||
Self::default() | ||
} | ||
|
||
/// Generate all tests for the given crate and output the Rust side to a file. | ||
pub fn generate<P: AsRef<Path>>(&mut self, crate_path: P, _output_file_path: P) -> Result<()> { | ||
let expanded = expand(crate_path)?; | ||
let ast = syn::parse_file(&expanded)?; | ||
/// Add a header to be included as part of the generated C file. | ||
/// | ||
/// The generate C test will be compiled by a C compiler, and this can be | ||
/// used to ensure that all the necessary header files are included to test | ||
/// all FFI definitions. | ||
pub fn header(&mut self, header: &str) -> &mut Self { | ||
self.headers.push(header.to_string()); | ||
self | ||
} | ||
|
||
/// Configures the target to compile C code for. | ||
/// | ||
/// Note that for Cargo builds this defaults to `$TARGET` and it's not | ||
/// necessary to call. | ||
pub fn target(&mut self, target: &str) -> &mut Self { | ||
self.target = Some(target.to_string()); | ||
self | ||
} | ||
|
||
/// Add a path to the C compiler header lookup path. | ||
/// | ||
/// This is useful for if the C library is installed to a nonstandard | ||
/// location to ensure that compiling the C file succeeds. | ||
pub fn include<P: AsRef<Path>>(&mut self, p: P) -> &mut Self { | ||
self.includes.push(p.as_ref().to_owned()); | ||
self | ||
} | ||
|
||
/// Configures the output directory of the generated Rust and C code. | ||
pub fn out_dir<P: AsRef<Path>>(&mut self, p: P) -> &mut Self { | ||
self.out_dir = Some(p.as_ref().to_owned()); | ||
self | ||
} | ||
|
||
/// Generate the Rust and C testing files. | ||
/// | ||
/// Returns the path to t generated file. | ||
pub fn generate_files( | ||
&mut self, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For each generated file, you should do a regex to replace There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, it looks like Askama has |
||
crate_path: impl AsRef<Path>, | ||
output_file_path: impl AsRef<Path>, | ||
) -> Result<PathBuf, GenerationError> { | ||
let expanded = expand(&crate_path).map_err(|e| { | ||
GenerationError::MacroExpansion(crate_path.as_ref().to_path_buf(), e.to_string()) | ||
})?; | ||
let ast = syn::parse_file(&expanded) | ||
.map_err(|e| GenerationError::RustSyntax(expanded, e.to_string()))?; | ||
|
||
let mut ffi_items = FfiItems::new(); | ||
ffi_items.visit_file(&ast); | ||
|
||
Ok(()) | ||
let output_directory = self | ||
.out_dir | ||
.clone() | ||
.unwrap_or_else(|| env::var("OUT_DIR").unwrap().into()); | ||
let output_file_path = output_directory.join(output_file_path); | ||
|
||
// Generate the Rust side of the tests. | ||
File::create(output_file_path.with_extension("rs")) | ||
.map_err(GenerationError::OsError)? | ||
.write_all( | ||
RustTestTemplate::new(&ffi_items) | ||
.render() | ||
.map_err(|e| { | ||
GenerationError::TemplateRender("Rust".to_string(), e.to_string()) | ||
})? | ||
.as_bytes(), | ||
) | ||
.map_err(GenerationError::OsError)?; | ||
|
||
// Generate the C side of the tests. | ||
// FIXME(ctest): Cpp not supported yet. | ||
let c_output_path = output_file_path.with_extension("c"); | ||
let headers = self.headers.iter().map(|h| h.as_str()).collect(); | ||
File::create(&c_output_path) | ||
.map_err(GenerationError::OsError)? | ||
.write_all( | ||
CTestTemplate::new(headers, &ffi_items) | ||
.render() | ||
.map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))? | ||
.as_bytes(), | ||
) | ||
.map_err(GenerationError::OsError)?; | ||
|
||
Ok(output_file_path) | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.