use crate::build_rs::BuildInfo;
use crate::cache;
use crate::deps::{self, SourceDependency};
use crate::gen::NativeLib;
use crate::spec::{self, ProviderSpecification};
use crate::{TracersError, TracersResult, TracingTarget, TracingType};
use failure::ResultExt;
use serde::{Deserialize, Serialize};
use std::env;
use std::io::Write;
use std::path::{Path, PathBuf};
mod target;
#[derive(Serialize, Deserialize)]
pub(crate) struct ProcessedFile {
dependencies: Vec<SourceDependency>,
providers: Vec<ProviderSpecification>,
}
#[derive(Serialize, Deserialize)]
pub(crate) struct ProcessedProviderTrait {
pub native_libs: Vec<NativeLib>,
}
trait NativeCodeGenerator {
fn generate_native_lib(&self) -> TracersResult<Vec<NativeLib>>;
fn out_dir(&self) -> &Path;
fn build_dir(&self) -> PathBuf {
self.out_dir().join("build")
}
fn output_dir(&self) -> PathBuf {
self.out_dir().join("output")
}
}
const PROCESSED_PROVIDER_KEY: &str = "processed_provider";
pub(crate) fn get_processed_provider_info(
provider: &ProviderSpecification,
) -> TracersResult<ProcessedProviderTrait> {
let out_dir = PathBuf::from(env::var("OUT_DIR").context("OUT_DIR")?);
let cache_dir = cache::get_cache_path(&out_dir);
cache::get_cached_object_computation(
&cache_dir,
provider.name(),
provider.hash(),
PROCESSED_PROVIDER_KEY,
)
.map_err(|e| TracersError::provider_trait_not_processed_error(provider.ident().to_string(), e))
}
pub(super) fn generate_native_code(
build_info: &BuildInfo,
stdout: &mut dyn Write,
manifest_dir: &Path,
out_dir: &Path,
_package_name: &str,
targets: Vec<PathBuf>,
) -> Vec<NativeLib> {
assert!(build_info.implementation.tracing_type() == TracingType::Static);
match build_info.implementation.tracing_target() {
TracingTarget::Disabled | TracingTarget::NoOp => {
writeln!(
stdout,
"No native code needed for {} tracing",
build_info.implementation.tracing_target().as_ref()
)
.unwrap();
vec![]
}
TracingTarget::Stap | TracingTarget::Lttng => {
let mut libs = Vec::new();
for target in targets.into_iter() {
let target_path = manifest_dir.join(&target);
writeln!(stdout, "Processing target {}", target_path.display()).unwrap();
libs.append(&mut process_file(build_info, stdout, out_dir, &target_path));
}
libs
}
}
}
fn process_file(
build_info: &BuildInfo,
stdout: &mut dyn Write,
out_dir: &Path,
file: &Path,
) -> Vec<NativeLib> {
let cache_dir = cache::get_cache_path(out_dir);
let result =
cache::cache_file_computation(&cache_dir, file, "processed-file", |file_contents| {
writeln!(
stdout,
"Generating {} implementation for target {}",
build_info.implementation.tracing_target().as_ref(),
file.display()
)
.unwrap();
let file: syn::File = syn::parse_file(file_contents).context("Parsing source file")?;
let dependencies = deps::get_dependencies(&file);
let providers = spec::find_providers(&build_info.package_name, &file);
Ok(ProcessedFile {
dependencies,
providers,
})
});
match result {
Ok(processed_file) => {
let mut libs = Vec::new();
for dependency in processed_file.dependencies.into_iter() {
match deps::resolve_dependency(file, &dependency) {
Ok(dep_file) => {
libs.append(&mut process_file(build_info, stdout, out_dir, &dep_file))
}
Err(_) => {
writeln!(stdout,
"cargo:warning=Unable to resove dependency {:?} in {}; any tracing providers it may contain will not be processed",
dependency,
file.display()
).unwrap();
}
}
}
for provider in processed_file.providers.into_iter() {
libs.append(&mut process_provider(build_info, stdout, out_dir, provider));
}
libs
}
Err(e) => {
writeln!(
stdout,
"cargo:warning=Error processing '{}': {}",
file.display(),
e
)
.unwrap();
writeln!(
stdout,
"cargo:warning=Code generation failed for '{}'. Tracing may not be available.",
file.display()
)
.unwrap();
vec![]
}
}
}
fn process_provider(
build_info: &BuildInfo,
stdout: &mut dyn Write,
out_dir: &Path,
provider: ProviderSpecification,
) -> Vec<NativeLib> {
let cache_dir = cache::get_cache_path(out_dir);
let name = provider.name().to_owned();
let ident = provider.ident().clone();
let result = cache::cache_object_computation(
&cache_dir,
&name,
provider.hash(),
PROCESSED_PROVIDER_KEY,
move || {
let generator = create_native_code_generator(build_info, out_dir, provider);
Ok(ProcessedProviderTrait {
native_libs: generator.generate_native_lib()?,
})
},
);
match result {
Ok(processed_provider) => {
processed_provider.native_libs
}
Err(e) => {
writeln!(
stdout,
"cargo:warning=Error generating tracing code for '{}': {}",
ident, e
)
.unwrap();
writeln!(
stdout,
"cargo:warning=Tracing may not be available for {}",
ident
)
.unwrap();
vec![]
}
}
}
fn create_native_code_generator(
build_info: &BuildInfo,
out_dir: &Path,
provider: ProviderSpecification,
) -> Box<dyn NativeCodeGenerator> {
match build_info.implementation.tracing_target() {
TracingTarget::Disabled | TracingTarget::NoOp => panic!(
"{} should never be passed to this function",
build_info.implementation.as_ref()
),
TracingTarget::Stap => Box::new(target::stap::StapNativeCodeGenerator::new(
out_dir, provider,
)),
TracingTarget::Lttng => Box::new(target::lttng::LttngNativeCodeGenerator::new(
out_dir, provider,
)),
}
}
#[cfg(test)]
#[cfg(target_os = "linux")]
mod tests {
use super::*;
use crate::testdata;
use crate::testdata::*;
use crate::TracingImplementation;
#[test]
fn caches_results() {
for implementation in [
TracingImplementation::StaticStap,
TracingImplementation::StaticLttng,
]
.iter()
{
let build_info = BuildInfo::new(TEST_CRATE_NAME.to_owned(), (*implementation).clone());
let temp_dir = tempfile::tempdir().unwrap();
let out_dir = temp_dir.path().join("out");
for first_run in [true, false].into_iter() {
for case in TEST_CRATES.iter() {
let guard = testdata::with_env_vars(vec![
("CARGO_PKG_NAME", case.package_name),
("CARGO_MANIFEST_DIR", case.root_directory.to_str().unwrap()),
("TARGET", "x86_64-linux-gnu"),
("HOST", "x86_64-linux-gnu"),
("OPT_LEVEL", "1"),
("OUT_DIR", out_dir.to_str().unwrap()),
]);
for target in case.targets.iter() {
let mut stdout = Vec::new();
process_file(
&build_info,
&mut stdout,
&out_dir,
&case.root_directory.join(target.entrypoint),
);
let output = String::from_utf8(stdout).unwrap();
if *first_run {
assert_ne!("", output, "test crate {}", case.root_directory.display());
} else {
let expected_errors = !target.expected_errors.is_empty();
if expected_errors {
assert_ne!(
"",
output,
"test crate {}",
case.root_directory.display()
);
} else {
let lines = output
.lines()
.filter(|line| {
!(line.starts_with("cargo:rustc-link-lib")
|| line.starts_with("cargo:rustc-link-search"))
})
.collect::<Vec<_>>()
.join("\n");
assert_eq!(
"",
lines,
"test crate {}",
case.root_directory.display()
);
}
}
}
drop(guard); }
}
}
}
#[test]
fn generates_processed_provider_trait() {
for test_trait in
get_test_provider_traits(|t: &TestProviderTrait| t.expected_error.is_none())
{
let (attr, item_trait) = test_trait.get_attr_and_item_trait();
let provider =
ProviderSpecification::from_trait(testdata::TEST_CRATE_NAME, attr, item_trait)
.unwrap();
for implementation in [
TracingImplementation::StaticStap,
TracingImplementation::StaticLttng,
]
.iter()
{
let build_info =
BuildInfo::new(TEST_CRATE_NAME.to_owned(), (*implementation).clone());
let temp_dir = tempfile::tempdir().unwrap();
let out_dir = temp_dir.path().join("out");
let lib_dir = out_dir.join("output");
let guard = testdata::with_env_vars(vec![
("TARGET", "x86_64-linux-gnu"),
("HOST", "x86_64-linux-gnu"),
("OPT_LEVEL", "1"),
("OUT_DIR", out_dir.to_str().unwrap()),
]);
let mut stdout = Vec::new();
process_provider(&build_info, &mut stdout, &out_dir, provider.clone());
let processed_provider = get_processed_provider_info(&provider)
.expect("There should be a processed provider");
assert_eq!(
vec![
NativeLib::StaticWrapperLib(provider.name_with_hash()),
NativeLib::StaticWrapperLibPath(lib_dir)
],
processed_provider
.native_libs
.into_iter()
.filter_map(|l| {
match l {
lib @ NativeLib::StaticWrapperLib(_) => Some(lib),
lib_path @ NativeLib::StaticWrapperLibPath(_) => Some(lib_path),
_ => None,
}
})
.collect::<Vec<_>>()
);
drop(guard);
}
}
}
}