#![allow(unused)]
use std::iter::FromIterator;
use std::collections::HashSet;
use std::convert::AsRef;
use std::path::{PathBuf, Path};
use std::string::ToString;
use tar::Archive;
use flate2::read::GzDecoder;
fn out_dir() -> PathBuf {
PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR env var"))
}
fn is_release_mode() -> bool {
has_env_var_with_value("PROFILE", "release")
}
fn is_debug_mode() -> bool {
has_env_var_with_value("PROFILE", "debug")
}
fn opt_level_eq(x: u8) -> bool {
has_env_var_with_value("OPT_LEVEL", &format!("{}", x))
}
fn has_env_var_with_value(s: &str, v: &str) -> bool {
std::env::var(s)
.map(|x| x.to_lowercase())
.map(|x| x == v.to_lowercase())
.unwrap_or(false)
}
pub fn extract_tar_file<P: AsRef<Path>, Q: AsRef<Path>>(tar_file: P, dest: Q) -> Result<(), String> {
let source = std::fs::read(tar_file).expect("read tar file");
let tar = GzDecoder::new(&source[..]);
let mut archive = Archive::new(tar);
let tmp_source_dir: Option<PathBuf> = {
archive
.unpack(&dest)
.map_err(|x| format!("[{:?}] failed to unpack tar file: {:?}", dest.as_ref(), x))?;
let xs = std::fs::read_dir(&dest)
.expect(&format!("unable to read dir {:?}", dest.as_ref()))
.filter_map(Result::ok)
.filter(|file| file.file_type().map(|x| x.is_dir()).unwrap_or(false))
.collect::<Vec<std::fs::DirEntry>>();
match &xs[..] {
[x] => Some(x.path()),
_ => None,
}
};
Ok(())
}
pub fn lookup_newest(paths: Vec<PathBuf>) -> Option<PathBuf> {
use std::time::{SystemTime, Duration};
let mut newest: Option<(PathBuf, Duration)> = None;
paths
.clone()
.into_iter()
.filter_map(|x: PathBuf| {
let timestamp = x
.metadata()
.ok()
.and_then(|y| y.created().ok())
.and_then(|x| x.duration_since(SystemTime::UNIX_EPOCH).ok());
match timestamp {
Some(y) => Some((x, y)),
_ => None
}
})
.for_each(|(x_path, x_created)| match &newest {
None => {
newest = Some((x_path, x_created));
}
Some((_, y_created)) => {
if &x_created > y_created {
newest = Some((x_path, x_created));
}
}
});
newest.map(|(x, _)| x)
}
pub fn files_with_prefix(dir: &PathBuf, pattern: &str) -> Vec<PathBuf> {
std::fs::read_dir(dir)
.expect(&format!("get dir contents: {:?}", dir))
.filter_map(Result::ok)
.filter_map(|x| {
let file_name = x
.file_name()
.to_str()?
.to_owned();
if file_name.starts_with(pattern) {
Some(x.path())
} else {
None
}
})
.collect::<Vec<_>>()
}
fn run_make(source_path: &PathBuf, makefile: &str) {
let result = std::process::Command::new("make")
.arg("-C")
.arg(source_path)
.arg("-f")
.arg(makefile)
.output()
.expect(&format!("make -C {:?} failed", source_path));
assert!(result.status.success());
}
fn cpy<P: AsRef<Path>, Q: AsRef<Path>>(from: P, to: Q) {
std::fs::copy(&from, &to)
.expect(&format!(
"unable to cpy from {:?} to {:?}",
from.as_ref(),
to.as_ref(),
));
}
pub const STATIC_LIBS: &[(&str, &str)] = &[
(
"avcodec",
"libavcodec/libavcodec.a",
),
(
"avdevice",
"libavdevice/libavdevice.a",
),
(
"avfilter",
"libavfilter/libavfilter.a",
),
(
"avformat",
"libavformat/libavformat.a",
),
(
"avutil",
"libavutil/libavutil.a",
),
(
"swresample",
"libswresample/libswresample.a",
),
(
"swscale",
"libswscale/libswscale.a",
),
];
pub const SEARCH_PATHS: &[&str] = &[
"libavcodec",
"libavdevice",
"libavfilter",
"libavformat",
"libavresample",
"libavutil",
"libpostproc",
"libswresample",
"libswscale",
];
#[derive(Debug, Clone)]
struct IgnoreMacros(HashSet<String>);
impl bindgen::callbacks::ParseCallbacks for IgnoreMacros {
fn will_parse_macro(&self, name: &str) -> bindgen::callbacks::MacroParsingBehavior {
if self.0.contains(name) {
bindgen::callbacks::MacroParsingBehavior::Ignore
} else {
bindgen::callbacks::MacroParsingBehavior::Default
}
}
}
fn build() {
let out_path = out_dir();
let source_path = out_path.join("FFmpeg-FFmpeg-2722fc2");
let already_built = {
STATIC_LIBS
.iter()
.map(|(_, x)| source_path.join(x))
.all(|x| x.exists())
};
let mut skip_build = already_built && !is_release_mode();
if has_env_var_with_value("FFDEV1", "1") {
skip_build = false;
}
if !source_path.exists() || !skip_build {
{
let result = std::process::Command::new("tar")
.arg("-xJf")
.arg("archive/FFmpeg-FFmpeg-2722fc2.tar.xz")
.arg("-C")
.arg(out_path.to_str().expect("PathBuf to str"))
.output()
.expect("tar decompression of ffmpeg source repo using xz (to fit the 10M crates limit)");
assert!(result.status.success());
}
assert!(source_path.exists());
}
if skip_build == false {
{
let mut configure_flags = vec![
"--disable-programs",
"--disable-doc",
"--disable-autodetect",
];
if is_debug_mode() && opt_level_eq(0) {
configure_flags.push("--disable-optimizations");
configure_flags.push("--disable-debug");
configure_flags.push("--disable-stripping");
}
let eval_configure = |flags: Vec<&str>| {
let flags = flags.join(" ");
std::process::Command::new("sh")
.arg("-c")
.arg(&format!(
"cd {path} && ./configure {flags}",
path=source_path.to_str().expect("PathBuf to str"),
flags=flags,
))
.output()
.expect(&format!("ffmpeg configure script"))
};
let result = eval_configure(configure_flags.clone());
if !result.status.success() {
let stderr = String::from_utf8(result.stderr).expect("invalid str");
let stdout = String::from_utf8(result.stdout).expect("invalid str");
let nasm_yasm_issue = stderr
.lines()
.chain(stdout.lines())
.any(|x| x.contains("nasm/yasm not found or too old"));
if nasm_yasm_issue {
configure_flags.push("--disable-x86asm");
let result = eval_configure(configure_flags);
if !result.status.success() {
let stderr = String::from_utf8(result.stderr).expect("invalid str");
let stdout = String::from_utf8(result.stdout).expect("invalid str");
panic!("configure failed:\n{}", vec![stderr, stdout].join("\n"));
}
} else {
panic!("configure failed:\n{}", vec![stderr, stdout].join("\n"));
}
}
}
{
let mut cpu_number = num_cpus::get();
let result = std::process::Command::new("make")
.arg("-C")
.arg(&source_path)
.arg("-f")
.arg("Makefile")
.arg(&format!("-j{}", cpu_number))
.output()
.expect(&format!("make -C {:?} failed", source_path));
assert!(result.status.success());
}
}
println!("cargo:rustc-link-search=native={}", source_path.to_str().expect("PathBuf to str"));
for path in SEARCH_PATHS {
println!("cargo:rustc-link-search=native={}", {
source_path.join(path).to_str().expect("PathBuf as str")
});
}
for (name, _) in STATIC_LIBS {
println!("cargo:rustc-link-lib=static={}", name);
}
{
println!("rerun-if-changed=headers");
let ffmpeg_headers = std::fs::read("headers").expect("unable to read headers file");
let ffmpeg_headers = String::from_utf8(ffmpeg_headers).expect("invalid utf8 file");
let ffmpeg_headers = ffmpeg_headers
.lines()
.collect::<Vec<&str>>();
assert!(
ffmpeg_headers
.iter()
.map(|x| x.trim())
.all(|x| !x.is_empty())
);
let gen_file_name = "bindings_ffmpeg.rs";
let ignored_macros = IgnoreMacros(HashSet::from_iter(vec![
String::from("FP_INFINITE"),
String::from("FP_NAN"),
String::from("FP_NORMAL"),
String::from("FP_SUBNORMAL"),
String::from("FP_ZERO"),
String::from("IPPORT_RESERVED"),
]));
let mut skip_codegen = out_path.join(gen_file_name).exists();
if has_env_var_with_value("FFDEV2", "2") {
skip_codegen = false;
}
let codegen = bindgen::Builder::default();
let codegen = codegen.clang_arg(format!("-I{}", source_path.to_str().expect("PathBuf to str")));
let mut missing = Vec::new();
let codegen = ffmpeg_headers
.iter()
.fold(codegen, |codegen: bindgen::Builder, path: &&str| -> bindgen::Builder {
let path: &str = path.clone();
let path: PathBuf = source_path.join(path);
let path: &str = path.to_str().expect("PathBuf to str");
if !PathBuf::from(path).exists() {
missing.push(String::from(path));
codegen
} else {
codegen.header(path)
}
});
if !missing.is_empty() {
panic!("missing headers: {:#?}", missing);
}
codegen
.parse_callbacks(Box::new(ignored_macros.clone()))
.rustfmt_bindings(true)
.detect_include_paths(true)
.generate_comments(true)
.generate()
.expect("Unable to generate bindings")
.write_to_file(out_path.join(gen_file_name))
.expect("Couldn't write bindings!");
}
}
fn main() {
build();
}