Use clang for inspecting macros instead of compiling the binary. (#11)

This commit is contained in:
Nebojsa Sabovic 2024-05-22 14:44:18 +02:00 committed by GitHub
parent b65d0de4b0
commit 8eb9fa688f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 115 additions and 166 deletions

View File

@ -190,6 +190,16 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 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]] [[package]]
name = "clang-sys" name = "clang-sys"
version = "1.7.0" version = "1.7.0"
@ -290,6 +300,7 @@ version = "2.0.0+ffmpeg-7.0"
dependencies = [ dependencies = [
"bindgen", "bindgen",
"cc", "cc",
"clang",
"libc", "libc",
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",

View File

@ -32,6 +32,7 @@ libc = "0.2"
cc = "1.0" cc = "1.0"
pkg-config = "0.3" pkg-config = "0.3"
bindgen = { version = "0.69", default-features = false, features = ["runtime"] } 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] [target.'cfg(target_env = "msvc")'.build-dependencies]
vcpkg = "0.2" vcpkg = "0.2"

View File

@ -1,11 +1,13 @@
extern crate bindgen; extern crate bindgen;
extern crate cc; extern crate cc;
extern crate clang;
extern crate pkg_config; extern crate pkg_config;
use std::collections::HashMap;
use std::env; use std::env;
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::fs::{self, File}; 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::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::str; use std::str;
@ -229,7 +231,6 @@ static AVFORMAT_FEATURES: &[AVFeature] = &[
AVFeature::new("ALLOW_FLUSH"), AVFeature::new("ALLOW_FLUSH"),
AVFeature::new("AVSTREAM_SIDE_DATA"), AVFeature::new("AVSTREAM_SIDE_DATA"),
AVFeature::new("GET_DUR_ESTIMATE_METHOD"), AVFeature::new("GET_DUR_ESTIMATE_METHOD"),
AVFeature::new("R_FRAME_RATE"),
]; ];
static AVDEVICE_FEATURES: &[AVFeature] = &[ static AVDEVICE_FEATURES: &[AVFeature] = &[
@ -715,135 +716,86 @@ fn add_pkg_config_path() {
fn add_pkg_config_path() {} fn add_pkg_config_path() {}
fn check_features(include_paths: &[PathBuf]) { fn check_features(include_paths: &[PathBuf]) {
let mut includes_code = String::new(); let clang = clang::Clang::new().expect("Cannot find clang");
let mut main_code = String::new(); let index = clang::Index::new(&clang, false, false);
for lib in LIBRARIES.iter().filter(|lib| lib.enabled()) { let enabled_libraries = || 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 include = format!("#include <{}>", header); let mut code = String::new();
if !includes_code.contains(&include) { for lib in enabled_libraries() {
includes_code.push_str(&include); let _ = writeln!(code, "#include <lib{}/{}.h>", lib.name, lib.name);
includes_code.push('\n');
} }
let _ = write!(
includes_code, let mut features_defined_enabled = enabled_libraries()
r#" .flat_map(|lib| lib.features)
#ifndef {var}_is_defined .map(|feature| {
#ifndef {var} let feature_name = format!("FF_API_{}", feature.name);
#define {var} 0 let _ = writeln!(code, "#ifdef {feature_name}");
#define {var}_is_defined 0 let _ = writeln!(
#else code,
#define {var}_is_defined 1 " int {} = {feature_name};",
#endif feature_name.to_lowercase()
#endif
"#,
var = var
); );
let _ = writeln!(code, "#endif");
(feature_name.to_lowercase(), (false, false))
})
.collect::<HashMap<_, _>>();
let _ = write!( let mut versions = enabled_libraries()
main_code, .map(|lib| (lib.name, (0, 0)))
r#"printf("[{var}]%d%d\n", {var}, {var}_is_defined); .collect::<HashMap<_, _>>();
"#,
var = var let include_args = include_paths
); .iter()
} .map(|path| format!("-I{}", path.to_string_lossy()))
} .collect::<Vec<_>>();
let version_check_info = [("avcodec", 56, 62, 0, 108)];
for &(lib, begin_version_major, end_version_major, begin_version_minor, end_version_minor) in let tu = index
version_check_info.iter() .parser("check.c")
{ .arguments(&include_args)
for version_major in begin_version_major..end_version_major { .detailed_preprocessing_record(true)
for version_minor in begin_version_minor..end_version_minor { .unsaved(&[clang::Unsaved::new("check.c", &code)])
let _ = write!( .parse()
main_code, .expect("Unable to parse generated file");
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}));
"#, tu.get_entity().visit_children(|entity, _parent| {
lib = lib, if let Some(name) = entity.get_name() {
lib_uppercase = lib.to_uppercase(), if entity.get_kind() == clang::EntityKind::VarDecl && name.starts_with("ff_api") {
version_major = version_major, if let Some(clang::EvaluationResult::SignedInteger(value)) = entity.evaluate() {
version_minor = version_minor if let Some(val) = features_defined_enabled.get_mut(&name) {
); *val = (true, value != 0);
}
}
}
}
clang::EntityVisitResult::Continue
});
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;
} }
} }
} }
let out_dir = output();
write!(
File::create(out_dir.join("check.c")).expect("Failed to create file"),
r#"
#include <stdio.h>
{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)) for (var, (var_defined, var_enabled)) in features_defined_enabled {
.current_dir(&out_dir) if var_enabled {
.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:rustc-cfg=feature="{}""#, var.to_lowercase());
println!(r#"cargo:{}=true"#, 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 // Also find out if defined or not (useful for cases where only the definition of a macro
// can be used as distinction) // can be used as distinction)
if &stdout[pos + 1..pos + 2] == "1" { if var_defined {
println!( println!(
r#"cargo:rustc-cfg=feature="{}_is_defined""#, r#"cargo:rustc-cfg=feature="{}_is_defined""#,
var.to_lowercase() var.to_lowercase()
@ -851,30 +803,24 @@ fn check_features(include_paths: &[PathBuf]) {
println!(r#"cargo:{}_is_defined=true"#, 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 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_major in begin_version_major..end_version_major {
for version_minor in begin_version_minor..end_version_minor { for version_minor in begin_version_minor..end_version_minor {
let search_str = format!( if libversion >= (version_major, version_minor) {
"[{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" {
println!( println!(
r#"cargo:rustc-cfg=feature="{}""#, r#"cargo:rustc-cfg=feature="{lib}_version_greater_than_{version_major}_{version_minor}""#
&search_str[1..(search_str.len() - 1)] );
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_6_1", 60, 31),
("ffmpeg_7_0", 61, 3), ("ffmpeg_7_0", 61, 3),
]; ];
for &(ffmpeg_version_flag, lavc_version_major, lavc_version_minor) in
ffmpeg_lavc_versions.iter() let lavc_version = *versions
{ .get("avcodec")
let search_str = format!( .expect("Unable to find the version for lib{lib}");
"[avcodec_version_greater_than_{lavc_version_major}_{lavc_version_minor}]",
lavc_version_major = lavc_version_major, for &(ffmpeg_version_flag, lavc_version_major, lavc_version_minor) in &ffmpeg_lavc_versions {
lavc_version_minor = lavc_version_minor - 1 if lavc_version >= (lavc_version_major, lavc_version_minor) {
);
let pos = stdout
.find(&search_str)
.expect("Variable not found in output")
+ search_str.len();
if &stdout[pos..pos + 1] == "1" {
println!(r#"cargo:rustc-cfg=feature="{}""#, ffmpeg_version_flag); println!(r#"cargo:rustc-cfg=feature="{}""#, ffmpeg_version_flag);
println!(r#"cargo:{}=true"#, ffmpeg_version_flag); println!(r#"cargo:{}=true"#, ffmpeg_version_flag);
} }
@ -998,10 +938,7 @@ fn main() {
let _ = pkgconfig.probe(&lib.lib_name()).unwrap(); let _ = pkgconfig.probe(&lib.lib_name()).unwrap();
} }
pkgconfig pkgconfig.probe("libavcodec").unwrap().include_paths
.probe("libavcodec")
.unwrap()
.include_paths
}; };
if statik && cfg!(target_os = "macos") { if statik && cfg!(target_os = "macos") {