From 8eb9fa688fcbbc2eff5cd37d936f0f016740077c Mon Sep 17 00:00:00 2001 From: Nebojsa Sabovic Date: Wed, 22 May 2024 14:44:18 +0200 Subject: [PATCH] Use clang for inspecting macros instead of compiling the binary. (#11) --- Cargo.lock.MSRV | 11 ++ ffmpeg-sys-the-third/Cargo.toml | 1 + ffmpeg-sys-the-third/build.rs | 269 ++++++++++++-------------------- 3 files changed, 115 insertions(+), 166 deletions(-) diff --git a/Cargo.lock.MSRV b/Cargo.lock.MSRV index 52107e8..96df275 100644 --- a/Cargo.lock.MSRV +++ b/Cargo.lock.MSRV @@ -190,6 +190,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clang" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c044c781163c001b913cd018fc95a628c50d0d2dfea8bca77dad71edb16e37" +dependencies = [ + "clang-sys", + "libc", +] + [[package]] name = "clang-sys" version = "1.7.0" @@ -290,6 +300,7 @@ version = "2.0.0+ffmpeg-7.0" dependencies = [ "bindgen", "cc", + "clang", "libc", "pkg-config", "vcpkg", diff --git a/ffmpeg-sys-the-third/Cargo.toml b/ffmpeg-sys-the-third/Cargo.toml index 58407bf..0d6a41b 100644 --- a/ffmpeg-sys-the-third/Cargo.toml +++ b/ffmpeg-sys-the-third/Cargo.toml @@ -32,6 +32,7 @@ libc = "0.2" cc = "1.0" pkg-config = "0.3" bindgen = { version = "0.69", default-features = false, features = ["runtime"] } +clang = { version = "2.0.0", features = ["clang_3_9", "runtime"] } [target.'cfg(target_env = "msvc")'.build-dependencies] vcpkg = "0.2" diff --git a/ffmpeg-sys-the-third/build.rs b/ffmpeg-sys-the-third/build.rs index b0816b2..ca79d0e 100644 --- a/ffmpeg-sys-the-third/build.rs +++ b/ffmpeg-sys-the-third/build.rs @@ -1,11 +1,13 @@ extern crate bindgen; extern crate cc; +extern crate clang; extern crate pkg_config; +use std::collections::HashMap; use std::env; use std::fmt::Write as FmtWrite; use std::fs::{self, File}; -use std::io::{self, BufRead, BufReader, Write}; +use std::io::{self, BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::process::Command; use std::str; @@ -229,7 +231,6 @@ static AVFORMAT_FEATURES: &[AVFeature] = &[ AVFeature::new("ALLOW_FLUSH"), AVFeature::new("AVSTREAM_SIDE_DATA"), AVFeature::new("GET_DUR_ESTIMATE_METHOD"), - AVFeature::new("R_FRAME_RATE"), ]; static AVDEVICE_FEATURES: &[AVFeature] = &[ @@ -715,166 +716,111 @@ fn add_pkg_config_path() { fn add_pkg_config_path() {} fn check_features(include_paths: &[PathBuf]) { - let mut includes_code = String::new(); - let mut main_code = String::new(); + let clang = clang::Clang::new().expect("Cannot find clang"); + let index = clang::Index::new(&clang, false, false); - for lib in LIBRARIES.iter().filter(|lib| lib.enabled()) { - let header = format!("lib{}/{}.h", lib.name, lib.name); - for feature in lib.features { - let var = format!("FF_API_{}", feature.name); + let enabled_libraries = || LIBRARIES.iter().filter(|lib| lib.enabled()); - let include = format!("#include <{}>", header); - if !includes_code.contains(&include) { - includes_code.push_str(&include); - includes_code.push('\n'); + let mut code = String::new(); + for lib in enabled_libraries() { + let _ = writeln!(code, "#include ", lib.name, lib.name); + } + + let mut features_defined_enabled = enabled_libraries() + .flat_map(|lib| lib.features) + .map(|feature| { + let feature_name = format!("FF_API_{}", feature.name); + let _ = writeln!(code, "#ifdef {feature_name}"); + let _ = writeln!( + code, + " int {} = {feature_name};", + feature_name.to_lowercase() + ); + let _ = writeln!(code, "#endif"); + (feature_name.to_lowercase(), (false, false)) + }) + .collect::>(); + + let mut versions = enabled_libraries() + .map(|lib| (lib.name, (0, 0))) + .collect::>(); + + let include_args = include_paths + .iter() + .map(|path| format!("-I{}", path.to_string_lossy())) + .collect::>(); + + let tu = index + .parser("check.c") + .arguments(&include_args) + .detailed_preprocessing_record(true) + .unsaved(&[clang::Unsaved::new("check.c", &code)]) + .parse() + .expect("Unable to parse generated file"); + + tu.get_entity().visit_children(|entity, _parent| { + if let Some(name) = entity.get_name() { + if entity.get_kind() == clang::EntityKind::VarDecl && name.starts_with("ff_api") { + if let Some(clang::EvaluationResult::SignedInteger(value)) = entity.evaluate() { + if let Some(val) = features_defined_enabled.get_mut(&name) { + *val = (true, value != 0); + } + } } - let _ = write!( - includes_code, - r#" - #ifndef {var}_is_defined - #ifndef {var} - #define {var} 0 - #define {var}_is_defined 0 - #else - #define {var}_is_defined 1 - #endif - #endif - "#, - var = var - ); + } + clang::EntityVisitResult::Continue + }); - let _ = write!( - main_code, - r#"printf("[{var}]%d%d\n", {var}, {var}_is_defined); - "#, - var = var - ); + for def in clang::sonar::find_definitions(tu.get_entity().get_children()) { + if let clang::sonar::DefinitionValue::Integer(_, value) = def.value { + if let Some(name) = def.name.strip_prefix("LIB") { + if let Some(name) = name.strip_suffix("_VERSION_MAJOR") { + if let Some(ver) = versions.get_mut(name.to_lowercase().as_str()) { + ver.0 = value; + } + } else if let Some(name) = name.strip_suffix("_VERSION_MINOR") { + if let Some(ver) = versions.get_mut(name.to_lowercase().as_str()) { + ver.1 = value; + } + } + } } } + + for (var, (var_defined, var_enabled)) in features_defined_enabled { + if var_enabled { + println!(r#"cargo:rustc-cfg=feature="{}""#, var.to_lowercase()); + println!(r#"cargo:{}=true"#, var.to_lowercase()); + } + + // Also find out if defined or not (useful for cases where only the definition of a macro + // can be used as distinction) + if var_defined { + println!( + r#"cargo:rustc-cfg=feature="{}_is_defined""#, + var.to_lowercase() + ); + println!(r#"cargo:{}_is_defined=true"#, var.to_lowercase()); + } + } + let version_check_info = [("avcodec", 56, 62, 0, 108)]; for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in - version_check_info.iter() + &version_check_info { + let libversion = *versions + .get(lib) + .expect("Unable to find the version for lib{lib}"); + for version_major in begin_version_major..end_version_major { for version_minor in begin_version_minor..end_version_minor { - let _ = write!( - main_code, - r#"printf("[{lib}_version_greater_than_{version_major}_{version_minor}]%d\n", LIB{lib_uppercase}_VERSION_MAJOR > {version_major} || (LIB{lib_uppercase}_VERSION_MAJOR == {version_major} && LIB{lib_uppercase}_VERSION_MINOR > {version_minor})); - "#, - lib = lib, - lib_uppercase = lib.to_uppercase(), - version_major = version_major, - version_minor = version_minor - ); - } - } - } - - let out_dir = output(); - - write!( - File::create(out_dir.join("check.c")).expect("Failed to create file"), - r#" - #include - {includes_code} - - int main() - {{ - {main_code} - return 0; - }} - "#, - includes_code = includes_code, - main_code = main_code - ) - .expect("Write failed"); - - let executable = out_dir.join(if cfg!(windows) { "check.exe" } else { "check" }); - let mut compiler = cc::Build::new() - .target(&env::var("HOST").unwrap()) // don't cross-compile this - .get_compiler() - .to_command(); - - for dir in include_paths { - compiler.arg("-I"); - compiler.arg(dir.to_string_lossy().into_owned()); - } - if !compiler - .current_dir(&out_dir) - .arg("-o") - .arg(&executable) - .arg("check.c") - .status() - .expect("Command failed") - .success() - { - panic!("Compile failed"); - } - - let check_output = Command::new(out_dir.join(&executable)) - .current_dir(&out_dir) - .output() - .expect("Check failed"); - if !check_output.status.success() { - panic!( - "{} failed: {}\n{}", - executable.display(), - String::from_utf8_lossy(&check_output.stdout), - String::from_utf8_lossy(&check_output.stderr) - ); - } - - let stdout = str::from_utf8(&check_output.stdout).unwrap(); - - println!("stdout of {}={}", executable.display(), stdout); - - for lib in LIBRARIES.iter().filter(|lib| lib.enabled()) { - for feature in lib.features { - let var = format!("FF_API_{}", feature.name); - let var_str = format!("[{var}]"); - let pos = var_str.len() - + stdout - .find(&var_str) - .unwrap_or_else(|| panic!("Variable '{}' not found in stdout output", var_str)); - if &stdout[pos..pos + 1] == "1" { - println!(r#"cargo:rustc-cfg=feature="{}""#, var.to_lowercase()); - println!(r#"cargo:{}=true"#, var.to_lowercase()); - } - - // Also find out if defined or not (useful for cases where only the definition of a macro - // can be used as distinction) - if &stdout[pos + 1..pos + 2] == "1" { - println!( - r#"cargo:rustc-cfg=feature="{}_is_defined""#, - var.to_lowercase() - ); - println!(r#"cargo:{}_is_defined=true"#, var.to_lowercase()); - } - } - } - - for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in - version_check_info.iter() - { - for version_major in begin_version_major..end_version_major { - for version_minor in begin_version_minor..end_version_minor { - let search_str = format!( - "[{lib}_version_greater_than_{version_major}_{version_minor}]", - version_major = version_major, - version_minor = version_minor, - lib = lib - ); - let pos = stdout - .find(&search_str) - .expect("Variable not found in output") - + search_str.len(); - - if &stdout[pos..pos + 1] == "1" { + if libversion >= (version_major, version_minor) { println!( - r#"cargo:rustc-cfg=feature="{}""#, - &search_str[1..(search_str.len() - 1)] + r#"cargo:rustc-cfg=feature="{lib}_version_greater_than_{version_major}_{version_minor}""# + ); + println!( + r#"cargo:{lib}_version_greater_than_{version_major}_{version_minor}=true"# ); - println!(r#"cargo:{}=true"#, &search_str[1..(search_str.len() - 1)]); } } } @@ -897,19 +843,13 @@ fn check_features(include_paths: &[PathBuf]) { ("ffmpeg_6_1", 60, 31), ("ffmpeg_7_0", 61, 3), ]; - for &(ffmpeg_version_flag, lavc_version_major, lavc_version_minor) in - ffmpeg_lavc_versions.iter() - { - let search_str = format!( - "[avcodec_version_greater_than_{lavc_version_major}_{lavc_version_minor}]", - lavc_version_major = lavc_version_major, - lavc_version_minor = lavc_version_minor - 1 - ); - let pos = stdout - .find(&search_str) - .expect("Variable not found in output") - + search_str.len(); - if &stdout[pos..pos + 1] == "1" { + + let lavc_version = *versions + .get("avcodec") + .expect("Unable to find the version for lib{lib}"); + + for &(ffmpeg_version_flag, lavc_version_major, lavc_version_minor) in &ffmpeg_lavc_versions { + if lavc_version >= (lavc_version_major, lavc_version_minor) { println!(r#"cargo:rustc-cfg=feature="{}""#, ffmpeg_version_flag); println!(r#"cargo:{}=true"#, ffmpeg_version_flag); } @@ -998,10 +938,7 @@ fn main() { let _ = pkgconfig.probe(&lib.lib_name()).unwrap(); } - pkgconfig - .probe("libavcodec") - .unwrap() - .include_paths + pkgconfig.probe("libavcodec").unwrap().include_paths }; if statik && cfg!(target_os = "macos") {