use crate::cargo;
use crate::error::{TracersError, TracersResult};
use crate::gen;
use crate::gen::NativeLib;
use crate::TracingImplementation;
use failure::ResultExt;
use serde::{Deserialize, Serialize};
use std::env;
use std::fs::File;
use std::io::Write;
use std::io::{BufReader, BufWriter};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
struct FeatureFlags {
enable_dynamic_tracing: bool,
enable_static_tracing: bool,
force_dyn_stap: bool,
force_dyn_noop: bool,
force_static_stap: bool,
force_static_lttng: bool,
force_static_noop: bool,
}
impl FeatureFlags {
pub fn from_env() -> TracersResult<FeatureFlags> {
Self::new(
Self::is_feature_enabled("dynamic-tracing"),
Self::is_feature_enabled("static-tracing"),
Self::is_feature_enabled("force-dyn-stap"),
Self::is_feature_enabled("force-dyn-noop"),
Self::is_feature_enabled("force-static-stap"),
Self::is_feature_enabled("force-static-lttng"),
Self::is_feature_enabled("force-static-noop"),
)
}
pub fn new(
enable_dynamic_tracing: bool,
enable_static_tracing: bool,
force_dyn_stap: bool,
force_dyn_noop: bool,
force_static_stap: bool,
force_static_lttng: bool,
force_static_noop: bool,
) -> TracersResult<FeatureFlags> {
if enable_dynamic_tracing && enable_static_tracing {
return Err(TracersError::code_generation_error("The features `dynamic-tracing` and `static-tracing` are mutually exclusive; please choose one"));
}
if force_dyn_stap && force_dyn_noop {
return Err(TracersError::code_generation_error("The features `force-dyn-stap` and `force_dyn_noop` are mutually exclusive; please choose one"));
}
if force_static_stap && force_static_noop {
return Err(TracersError::code_generation_error("The features `force-static-stap` and `force_static_noop` are mutually exclusive; please choose one"));
}
if force_static_lttng && force_static_noop {
return Err(TracersError::code_generation_error("The features `force-static-lttng` and `force_static_noop` are mutually exclusive; please choose one"));
}
Ok(FeatureFlags {
enable_dynamic_tracing,
enable_static_tracing,
force_dyn_stap,
force_dyn_noop,
force_static_stap,
force_static_lttng,
force_static_noop,
})
}
pub fn enable_tracing(&self) -> bool {
self.enable_dynamic() || self.enable_static()
}
pub fn enable_dynamic(&self) -> bool {
self.enable_dynamic_tracing || self.force_dyn_noop || self.force_dyn_stap
}
pub fn enable_static(&self) -> bool {
self.enable_static_tracing
}
pub fn force_dyn_stap(&self) -> bool {
self.force_dyn_stap
}
pub fn force_dyn_noop(&self) -> bool {
self.force_dyn_noop
}
pub fn force_static_stap(&self) -> bool {
self.force_static_stap
}
pub fn force_static_lttng(&self) -> bool {
self.force_static_lttng
}
fn is_feature_enabled(name: &str) -> bool {
env::var(&format!(
"CARGO_FEATURE_{}",
name.to_uppercase().replace("-", "_")
))
.is_ok()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub(crate) struct BuildInfo {
pub package_name: String,
pub implementation: TracingImplementation,
}
impl BuildInfo {
pub fn new(package_name: String, implementation: TracingImplementation) -> BuildInfo {
BuildInfo {
package_name,
implementation,
}
}
pub fn load() -> TracersResult<BuildInfo> {
let path = Self::get_build_path()?;
let file = File::open(&path)
.map_err(|e| TracersError::build_info_read_error(path.clone(), e.into()))?;
let reader = BufReader::new(file);
serde_json::from_reader(reader)
.map_err(|e| TracersError::build_info_read_error(path.clone(), e.into()))
}
pub fn save(&self) -> TracersResult<PathBuf> {
let path = Self::get_build_path()?;
path.parent()
.map(|p| {
std::fs::create_dir_all(p)
.map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))
})
.unwrap_or(Ok(()))?;
let file = File::create(&path)
.map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))?;
let writer = BufWriter::new(file);
serde_json::to_writer(writer, self)
.map_err(|e| TracersError::build_info_write_error(path.clone(), e.into()))?;
Ok(path)
}
fn get_build_path() -> TracersResult<PathBuf> {
if "tracers" == env::var("CARGO_PKG_NAME").ok().unwrap_or_default() {
let rel_path = PathBuf::from(&format!(
"{}-{}/buildinfo.json",
env::var("CARGO_PKG_NAME").context("CARGO_PKG_NAME")?,
env::var("CARGO_PKG_VERSION").context("CARGO_PKG_VERSION")?
));
Ok(PathBuf::from(env::var("OUT_DIR").context("OUT_DIR")?).join(rel_path))
} else if let Ok(build_info_path) = env::var("DEP_TRACERS_BUILD_INFO_PATH") {
Ok(PathBuf::from(build_info_path))
} else if let Ok(build_info_path) = env::var("TRACERS_BUILD_INFO_PATH") {
Ok(PathBuf::from(build_info_path))
} else {
Err(TracersError::missing_call_in_build_rs())
}
}
}
pub fn build() {
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
match build_internal(&mut stdout) {
Ok(_) => writeln!(stdout, "probes build succeeded").unwrap(),
Err(e) => {
writeln!(stderr, "Error building probes: {}", e).unwrap();
std::process::exit(-1);
}
};
}
fn build_internal<OUT: Write>(out: &mut OUT) -> TracersResult<()> {
let build_info_path = BuildInfo::get_build_path()?;
writeln!(
out,
"cargo:rustc-env=TRACERS_BUILD_INFO_PATH={}",
build_info_path.display()
)
.unwrap();
generate_native_code(out)
}
pub fn tracers_build() {
let mut stdout = std::io::stdout();
let mut stderr = std::io::stderr();
let features = FeatureFlags::from_env().expect("Invalid feature flags");
match tracers_build_internal(&mut stdout, features) {
Ok(_) => {}
Err(e) => {
writeln!(stderr, "{}", e).unwrap();
panic!("tracers build failed: {}", e);
}
}
}
fn tracers_build_internal<OUT: Write>(out: &mut OUT, features: FeatureFlags) -> TracersResult<()> {
writeln!(out, "Detected features: \n{:?}", features).unwrap();
select_implementation(&features).map(|implementation| {
if implementation.is_enabled() {
writeln!(out, "cargo:rustc-cfg=enabled").unwrap();
writeln!(out,
"cargo:rustc-cfg={}_enabled",
if implementation.is_static() {
"static"
} else {
"dynamic"
}
).unwrap();
writeln!(out, "cargo:rustc-cfg={}_enabled", implementation.as_ref()).unwrap();
}
let build_info = BuildInfo::new(env::var("CARGO_PKG_NAME").expect("CARGO_PKG_NAME"), implementation);
match build_info.save() {
Ok(build_info_path) => {
writeln!(out, "cargo:build-info-path={}", build_info_path.display()).unwrap();
}
Err(e) => {
writeln!(out, "cargo:warning=Error saving build info file; some targets may fail to build. Error details: {}", e).unwrap();
}
}
})?;
generate_native_code(out)
}
fn select_implementation(features: &FeatureFlags) -> TracersResult<TracingImplementation> {
if !features.enable_tracing() {
return Ok(TracingImplementation::Disabled);
}
if features.enable_dynamic() {
if features.force_dyn_stap() {
if env::var("DEP_TRACERS_DYN_STAP_SUCCEEDED").is_err() {
return Err(TracersError::code_generation_error(
"force-dyn-stap is enabled but the dyn_stap library is not available",
));
} else {
return Ok(TracingImplementation::DynamicStap);
}
} else if features.force_dyn_noop() {
return Ok(TracingImplementation::DynamicNoOp);
}
if env::var("DEP_TRACERS_DYN_STAP_SUCCEEDED").is_ok() {
Ok(TracingImplementation::DynamicStap)
} else {
Ok(TracingImplementation::DynamicNoOp)
}
} else {
assert!(features.enable_static());
if features.force_static_stap() {
Ok(TracingImplementation::StaticStap)
} else if features.force_static_lttng() {
Ok(TracingImplementation::StaticLttng)
} else {
Ok(TracingImplementation::StaticNoOp)
}
}
}
fn generate_native_code(out: &mut dyn Write) -> TracersResult<()> {
let manifest_dir = env::var("CARGO_MANIFEST_DIR").context(
"CARGO_MANIFEST_DIR is not set; are you sure you're calling this from within build.rs?",
)?;
let manifest_path = PathBuf::from(manifest_dir).join("Cargo.toml");
let package_name = env::var("CARGO_PKG_NAME").unwrap();
let targets = cargo::get_targets(&manifest_path, &package_name).context("get_targets")?;
let out_path = &PathBuf::from(env::var("OUT_DIR").context("OUT_DIR")?);
let mut native_libs = gen::code_generator()?.generate_native_code(
out,
&Path::new(&manifest_path),
&out_path,
&package_name,
targets,
);
native_libs.sort_by(|a, b| a.partial_cmp(b).unwrap());
native_libs.dedup();
for native_lib in native_libs.into_iter() {
match native_lib {
NativeLib::StaticWrapperLib(_) => {
}
NativeLib::StaticWrapperLibPath(path) | NativeLib::SupportLibPath(path) => {
println!("cargo:rustc-link-search=native={}", path.display());
}
NativeLib::DynamicSupportLib(lib) => {
println!("cargo:rustc-link-lib=dylib={}", lib);
}
NativeLib::StaticSupportLib(lib) => {
println!("cargo:rustc-link-lib=static={}", lib);
}
};
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testdata;
use crate::TracingType;
#[test]
#[should_panic]
fn tracers_build_panics_invalid_features() {
let guard = testdata::with_env_vars(vec![
("CARGO_FEATURE_STATIC_TRACING", "1"),
("CARGO_FEATURE_DYNAMIC_TRACING", "1"),
]);
tracers_build();
drop(guard);
}
#[test]
fn build_rs_workflow_tests() {
let test_cases = vec![
(
FeatureFlags::new(false, false, false, false, false, false, false).unwrap(),
TracingImplementation::Disabled,
),
(
FeatureFlags::new(true, false, false, false, false, false, false).unwrap(),
TracingImplementation::DynamicNoOp,
),
(
FeatureFlags::new(false, true, false, false, false, false, false).unwrap(),
TracingImplementation::StaticNoOp,
),
(
FeatureFlags::new(false, true, false, false, false, false, true).unwrap(),
TracingImplementation::StaticNoOp,
),
(
FeatureFlags::new(false, true, false, false, true, false, false).unwrap(),
TracingImplementation::StaticStap,
),
(
FeatureFlags::new(false, true, false, false, false, true, false).unwrap(),
TracingImplementation::StaticLttng,
),
];
let temp_dir = tempfile::tempdir().unwrap();
let manifest_dir = env!("CARGO_MANIFEST_DIR");
let out_dir = temp_dir.path().join("out");
for (features, expected_impl) in test_cases.into_iter() {
let context = format!(
"features: {:?}\nexpected_impl: {}",
features,
expected_impl.as_ref()
);
let guard = testdata::with_env_vars(vec![
("CARGO_PKG_NAME", "tracers"),
("CARGO_PKG_VERSION", "1.2.3"),
("CARGO_MANIFEST_DIR", manifest_dir),
("OUT_DIR", out_dir.to_str().unwrap()),
]);
let mut stdout = Vec::new();
tracers_build_internal(&mut stdout, features.clone())
.expect(&format!("Unexpected failure with features: {:?}", features));
let build_info_path = BuildInfo::get_build_path().unwrap();
let step1_build_info = BuildInfo::load().expect(&format!(
"Failed to load build info for features: {:?}",
features
));
assert_eq!(
expected_impl, step1_build_info.implementation,
"context: {}",
context
);
let output = String::from_utf8(stdout).unwrap();
assert!(
output.contains(&format!(
"cargo:build-info-path={}",
build_info_path.display()
)),
context
);
match expected_impl.tracing_type() {
TracingType::Disabled => assert!(!output.contains("enabled")),
TracingType::Dynamic => assert!(output.contains("cargo:rustc-cfg=dynamic_enabled")),
TracingType::Static => assert!(output.contains("cargo:rustc-cfg=static_enabled")),
}
if expected_impl.is_enabled() {
assert!(
output.contains(&format!(
"cargo:rustc-cfg={}_enabled",
expected_impl.as_ref()
)),
context
);
}
drop(guard);
let guard = testdata::with_env_vars(vec![
(
"DEP_TRACERS_BUILD_INFO_PATH",
build_info_path.to_str().unwrap(),
),
("OUT_DIR", out_dir.to_str().unwrap()),
]);
let step2_build_info = BuildInfo::load().expect(&format!(
"Failed to load build info for features: {:?}",
features
));
assert_eq!(step1_build_info, step2_build_info, "context: {}", context);
drop(guard);
for test_case in testdata::TEST_CRATES.iter() {
let context = format!(
"features: {:?} test_case: {}",
features,
test_case.root_directory.display()
);
let mut stdout = Vec::new();
let guard = testdata::with_env_vars(vec![
(
"DEP_TRACERS_BUILD_INFO_PATH",
build_info_path.to_str().unwrap(),
),
("OUT_DIR", out_dir.to_str().unwrap()),
("CARGO_PKG_NAME", test_case.package_name),
(
"CARGO_MANIFEST_DIR",
test_case.root_directory.to_str().unwrap(),
),
("TARGET", "x86_64-linux-gnu"),
("HOST", "x86_64-linux-gnu"),
("OPT_LEVEL", "1"),
]);
build_internal(&mut stdout).expect(&context);
let output = String::from_utf8(stdout).unwrap();
assert!(output.contains(&format!(
"cargo:rustc-env=TRACERS_BUILD_INFO_PATH={}",
build_info_path.display()
)));
drop(guard);
}
let guard = testdata::with_env_vars(vec![(
"TRACERS_BUILD_INFO_PATH",
build_info_path.to_str().unwrap(),
)]);
let step3_build_info = BuildInfo::load().expect(&format!(
"Failed to load build info for features: {:?}",
features
));
assert_eq!(step1_build_info, step3_build_info, "context: {}", context);
drop(guard);
}
}
}