ndbioimage 0.2.0

Read bio image formats using the bio-formats java package.
#[cfg(feature = "bioformats_java")]
const BIOFORMATS_VERSION: &str = "8.5.0";

#[cfg(feature = "bioformats_java")]
mod bioformats {
    use std::error::Error;
    use std::fmt::{Display, Formatter};

    #[derive(Clone, Debug)]
    pub(crate) enum BuildError {
        J4rsVersionNotFound,
    }

    impl Display for BuildError {
        fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> {
            match self {
                Self::J4rsVersionNotFound => write!(fmt, "J4rsVersion not found in Cargo.lock"),
            }
        }
    }

    impl Error for BuildError {}

    fn get_j4rs_version() -> Result<String, Box<dyn Error>> {
        let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
        let lock_path = std::path::Path::new(&manifest_dir).join("Cargo.lock");
        let lock_toml = std::fs::read_to_string(&lock_path)?;
        let value: toml::Value = toml::from_str(&lock_toml)?;
        if let Some(packages) = value.get("package").and_then(|v| v.as_array()) {
            for package in packages {
                if let (Some(name), Some(version)) = (
                    package.get("name").and_then(|v| v.as_str()),
                    package.get("version"),
                ) && name == "j4rs"
                {
                    return Ok(version
                        .to_string()
                        .strip_prefix("\"")
                        .and_then(|v| v.strip_suffix("\""))
                        .ok_or(BuildError::J4rsVersionNotFound)?
                        .to_string());
                }
            }
        }
        Err(Box::new(BuildError::J4rsVersionNotFound {}))
    }

    pub(crate) fn build() -> Result<(), Box<dyn Error>> {
        let j4rs_version = get_j4rs_version()?;
        let out_dir = std::env::var("OUT_DIR")?;
        let dest_path = std::path::Path::new(&out_dir).join("constants.rs");
        let contents = format!(
            r#"
/// Generated by build.rs
pub(super) const J4RS_VERSION: &str = "{}";
pub(super) const BIOFORMATS_VERSION: &str = "{}";
            "#,
            j4rs_version,
            crate::BIOFORMATS_VERSION,
        );
        std::fs::write(&dest_path, contents)?;
        Ok(())
    }
}

#[cfg(all(not(feature = "python"), feature = "bioformats_java"))]
mod no_python_bioformats {
    use j4rs::errors::J4RsError;
    use j4rs::{JvmBuilder, MavenArtifact, MavenArtifactRepo, MavenSettings};
    use retry::delay::Exponential;
    use retry::{delay, retry};
    use std::error::Error;
    use std::fmt::{Display, Formatter};

    #[derive(Clone, Debug)]
    pub(crate) enum BuildError {
        BioFormatsNotDownloaded,
    }

    impl Display for BuildError {
        fn fmt(&self, fmt: &mut Formatter) -> Result<(), std::fmt::Error> {
            write!(fmt, "Bioformats package not downloaded")
        }
    }

    impl Error for BuildError {}

    pub(crate) fn build() -> Result<(), Box<dyn Error>> {
        retry(
            Exponential::from_millis(1000).map(delay::jitter).take(4),
            deploy_java_artifacts,
        )?;
        let path = default_jassets_path()?;
        if !path.join("bioformats_package-8.5.0.jar").exists() {
            Err(BuildError::BioFormatsNotDownloaded)?;
        }
        Ok(())
    }

    fn default_jassets_path() -> Result<std::path::PathBuf, J4RsError> {
        let is_build_script = std::env::var("OUT_DIR").is_ok();

        let mut start_path = if is_build_script {
            std::path::PathBuf::from(std::env::var("OUT_DIR")?)
        } else {
            std::env::current_exe()?
        };
        start_path = std::fs::canonicalize(start_path)?;

        while start_path.pop() {
            for entry in std::fs::read_dir(&start_path)? {
                let path = entry?.path();
                if path.file_name().map(|x| x == "jassets").unwrap_or(false) {
                    return Ok(path);
                }
            }
        }

        Err(J4RsError::GeneralError(
            "Can not find jassets directory".to_string(),
        ))
    }

    fn deploy_java_artifacts() -> Result<(), J4RsError> {
        let jvm = JvmBuilder::new()
            .skip_setting_native_lib()
            .with_maven_settings(MavenSettings::new(vec![MavenArtifactRepo::from(
                "openmicroscopy::https://artifacts.openmicroscopy.org/artifactory/ome.releases",
            )]))
            .build()?;

        jvm.deploy_artifact(&MavenArtifact::from("ome:bioformats_package:8.5.0"))?;

        #[cfg(feature = "gpl-formats")]
        jvm.deploy_artifact(&MavenArtifact::from("ome:formats-gpl:8.5.0"))?;

        Ok(())
    }
}

#[cfg(all(feature = "python", feature = "bioformats_java"))]
mod python_bioformats {
    pub(crate) fn build() -> Result<(), Box<dyn std::error::Error>> {
        let py_src_path = std::env::current_dir()?.join("py").join("ndbioimage");
        let py_jassets_path = py_src_path.join("jassets");
        let py_deps_path = py_src_path.join("deps");
        if py_jassets_path.exists() {
            std::fs::remove_dir_all(&py_jassets_path)?;
        }
        if py_deps_path.exists() {
            std::fs::remove_dir_all(&py_deps_path)?;
        }

        j4rs::Jvm::copy_j4rs_libs_under(py_src_path.to_str().unwrap())?;
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("cargo::rerun-if-changed=build.rs");

    if std::env::var("DOCS_RS").is_err() {
        #[cfg(feature = "movie")]
        ffmpeg_sidecar::download::auto_download()?;

        #[cfg(feature = "bioformats_java")]
        bioformats::build()?;

        #[cfg(all(not(feature = "python"), feature = "bioformats_java"))]
        no_python_bioformats::build()?;

        #[cfg(all(feature = "python", feature = "bioformats_java"))]
        python_bioformats::build()?;
    }

    Ok(())
}