Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

SSL build.rs   Interaktion und
Portierbarkeitunbekannt

 
rahmenlose Ansicht.rs DruckansichtUnknown {[0] [0] [0]}Mathematik

// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

#![expect(
    clippy::unwrap_used,
    clippy::iter_over_hash_type,
    reason = "OK in a build script."
)]

use std::{
    collections::{HashMap, HashSet},
    env,
    error::Error,
    fs,
    path::{Path, PathBuf},
    process::Command,
};

use bindgen::Builder;
use semver::{Version, VersionReq};
use serde_derive::Deserialize;

#[path = "src/min_version.rs"]
mod min_version;
use min_version::MINIMUM_NSS_VERSION;

const BINDINGS_DIR: &str = "bindings";
const BINDINGS_CONFIG: &str = "bindings.toml";

// This is the format of a single section of the configuration file.
#[derive(Deserialize)]
struct Bindings {
    /// types that are explicitly included
    #[serde(default)]
    types: Vec<String>,
    /// functions that are explicitly included
    #[serde(default)]
    functions: Vec<String>,
    /// variables (and `#define`s) that are explicitly included
    #[serde(default)]
    variables: Vec<String>,
    /// types that should be explicitly marked as opaque
    #[serde(default)]
    opaque: Vec<String>,
    /// enumerations that are turned into a module (without this, the enum is
    /// mapped using the default, which means that the individual values are
    /// formed with an underscore as <`enum_type`>_<`enum_value_name`>).
    #[serde(default)]
    enums: Vec<String>,

    /// Any item that is specifically excluded; if none of the types, functions,
    /// or variables fields are specified, everything defined will be mapped,
    /// so this can be used to limit that.
    #[serde(default)]
    exclude: Vec<String>,

    /// Whether the file is to be interpreted as C++
    #[serde(default)]
    cplusplus: bool,
}

// bindgen needs access to libclang.
// On windows, this doesn't just work, you have to set LIBCLANG_PATH.
// Rather than download the 400Mb+ files, like gecko does, let's just reuse their work.
// On macOS, clang-sys prefers the highest-versioned libclang it can find, which may be a
// Homebrew LLVM that doesn't have the correct macOS SDK include paths, resulting in broken
// bindings. Force use of Xcode's libclang instead.
fn setup_clang() {
    println!("cargo:rerun-if-env-changed=LIBCLANG_PATH");
    println!("cargo:rerun-if-env-changed=CI");
    // In CI, the environment is already configured correctly.
    if env::var("CI").is_ok() {
        return;
    }
    if env::var("LIBCLANG_PATH").is_ok() {
        return;
    }
    if env::consts::OS == "macos" {
        if let Ok(output) = Command::new("xcode-select").arg("--print-path").output() {
            if output.status.success() {
                let xcode_path = String::from_utf8_lossy(&output.stdout).trim().to_string();
                let candidates = [
                    PathBuf::from(&xcode_path).join("Toolchains/XcodeDefault.xctoolchain/usr/lib"),
                    PathBuf::from(&xcode_path).join("usr/lib"),
                ];
                if let Some(libclang_dir) = candidates.iter().find(|p| p.is_dir()) {
                    unsafe {
                        env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
                    }
                } else {
                    println!(
                        "cargo:warning=Xcode toolchain libclang not found at {}; set LIBCLANG_PATH if build fails",
                        candidates[0].display()
                    );
                }
            } else {
                println!(
                    "cargo:warning=xcode-select returned an error; set LIBCLANG_PATH if build fails"
                );
            }
        } else {
            println!("cargo:warning=xcode-select not found; set LIBCLANG_PATH if build fails");
        }
    } else if env::consts::OS == "windows" {
        println!("cargo:rerun-if-env-changed=MOZBUILD_STATE_PATH");
        let mozbuild_root = if let Ok(dir) = env::var("MOZBUILD_STATE_PATH") {
            PathBuf::from(dir.trim())
        } else {
            println!("cargo:warning=Building without a gecko setup is not likely to work.");
            println!("cargo:warning=A working libclang is needed to build nss-rs.");
            println!("cargo:warning=Either LIBCLANG_PATH or MOZBUILD_STATE_PATH needs to be set.");
            println!(
                "cargo:warning=We recommend checking out https://github.com/mozilla/gecko-dev"
            );
            println!("cargo:warning=Then run `./mach bootstrap` which will retrieve clang.");
            println!("cargo:warning=Make sure to export MOZBUILD_STATE_PATH when building.");
            return;
        };
        let libclang_dir = mozbuild_root.join("clang").join("lib");
        if libclang_dir.is_dir() {
            unsafe {
                env::set_var("LIBCLANG_PATH", libclang_dir.to_str().unwrap());
            }
        } else {
            println!(
                "cargo:warning=LIBCLANG_PATH isn't set; maybe run ./mach bootstrap with gecko"
            );
        }
    }
}

fn nss_dir() -> String {
    let dir = env::var("NSS_DIR").map_or_else(
        |_| {
            let out_dir = env::var("OUT_DIR").unwrap();
            let dir = Path::new(&out_dir).join("nss");
            if !dir.exists() {
                Command::new("hg")
                    .args([
                        "clone",
                        "https://hg.mozilla.org/projects/nss",
                        dir.to_str().unwrap(),
                    ])
                    .status()
                    .expect("can't clone nss");
            }
            let nspr_dir = Path::new(&out_dir).join("nspr");
            if !nspr_dir.exists() {
                Command::new("hg")
                    .args([
                        "clone",
                        "https://hg.mozilla.org/projects/nspr",
                        nspr_dir.to_str().unwrap(),
                    ])
                    .status()
                    .expect("can't clone nspr");
            }
            dir
        },
        |dir| {
            let path = PathBuf::from(dir.trim());
            assert!(
                !path.is_relative(),
                "The NSS_DIR environment variable is expected to be an absolute path."
            );
            path
        },
    );
    assert!(dir.is_dir(), "NSS_DIR {} doesn't exist", dir.display());
    // Note that this returns a relative path because UNC
    // paths on windows cause certain tools to explode.
    dir.to_string_lossy().to_string()
}

fn get_bash() -> PathBuf {
    // If BASH is set, use that.
    if let Ok(bash) = env::var("BASH") {
        return PathBuf::from(bash);
    }

    // When running under MOZILLABUILD, we need to make sure not to invoke
    // another instance of bash that might be sitting around (like WSL).
    env::var("MOZILLABUILD").map_or_else(
        |_| PathBuf::from("bash"),
        |d| PathBuf::from(d).join("msys").join("bin").join("bash.exe"),
    )
}

fn build_nss(dir: PathBuf) {
    let mut build_nss = vec![
        String::from("./build.sh"),
        String::from("-Ddisable_tests=1"),
        String::from("-Ddisable_dbm=1"),
        String::from("-Ddisable_libpkix=1"),
        String::from("-Ddisable_ckbi=1"),
        String::from("-Ddisable_fips=1"),
        String::from("--opt"),
        // Generate static libraries in addition to shared libraries.
        String::from("--static"),
    ];
    if env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "aarch64" {
        build_nss.push(String::from("--target=arm64"));
    }
    let status = Command::new(get_bash())
        .args(build_nss)
        .current_dir(dir)
        .status()
        .expect("couldn't start NSS build");
    assert!(status.success(), "NSS build failed");
}

fn dynamic_link() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let dynamic_libs = if target_os == "windows" {
        [
            "nssutil3.dll",
            "nss3.dll",
            "ssl3.dll",
            "libplds4.dll",
            "libplc4.dll",
            "libnspr4.dll",
        ]
    } else {
        ["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"]
    };
    for lib in dynamic_libs {
        println!("cargo:rustc-link-lib=dylib={lib}");
    }
    maybe_link_freebl3();
}

fn maybe_link_freebl3() {
    if env::var("CARGO_FEATURE_BLAPI").is_ok() {
        println!("cargo:rustc-link-lib=dylib=freebl3");
    }
}

/// Emit the library link and search-path for freebl3 in a Gecko build.
#[cfg(feature = "gecko")]
fn maybe_link_freebl3_gecko(topobjdir: &Path, fold_libs: bool) {
    if env::var("CARGO_FEATURE_BLAPI").is_err() {
        return;
    }
    println!("cargo:rustc-link-lib=dylib=freebl3");
    let search = if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") {
        topobjdir
            .join("security")
            .join("nss")
            .join("lib")
            .join("freebl")
            .join("freebl_freebl3")
    } else if fold_libs {
        topobjdir.join("dist").join("bin")
    } else {
        return;
    };
    println!("cargo:rustc-link-search=native={}", search.display());
}

fn static_link() {
    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
    let mut static_libs = vec![
        "certdb",
        "certhi",
        "cryptohi",
        "freebl_static",
        if target_os == "windows" {
            "libnspr4"
        } else {
            "nspr4"
        },
        "gcm",
        "nss_static",
        "nssb",
        "nssdev",
        "nsspki",
        "nssutil",
        "pk11wrap_static",
        if target_os == "windows" {
            "libplc4"
        } else {
            "plc4"
        },
        if target_os == "windows" {
            "libplds4"
        } else {
            "plds4"
        },
        "softokn_static",
        "ssl",
    ];
    // macOS always dynamically links against the system sqlite library.
    // See https://github.com/nss-dev/nss/blob/a8c22d8fc0458db3e261acc5e19b436ab573a961/coreconf/Darwin.mk#L130-L135
    if target_os == "macos" {
        println!("cargo:rustc-link-lib=dylib=sqlite3");
    } else {
        static_libs.push("sqlite");
    }
    // Hardware specific libs.
    let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
    if target_arch == "arm" || target_arch == "aarch64" {
        static_libs.push("armv8_c_lib");
    }
    if target_arch == "arm" {
        static_libs.push("ghash-aes-arm32-neon_c_lib");
    }
    if target_arch == "aarch64" {
        static_libs.push("ghash-aes-aarch64_c_lib");
    }
    if target_arch == "x86_64" || target_arch == "x86" {
        static_libs.push("ghash-aes-x86_c_lib");
        static_libs.push("sha-x86_c_lib");
    }
    if target_arch == "x86_64" {
        static_libs.push("hw-acc-crypto-avx");
        static_libs.push("hw-acc-crypto-avx2");
        static_libs.push("intel-gcm-wrap_c_lib");
    }
    for lib in static_libs {
        println!("cargo:rustc-link-lib=static={lib}");
    }
}

fn get_includes(nsstarget: &Path, nssdist: &Path) -> Vec<PathBuf> {
    let nsprinclude = nsstarget.join("include").join("nspr");
    let nssinclude = nssdist.join("public").join("nss");
    vec![nsprinclude, nssinclude]
}

fn build_bindings(base: &str, bindings: &Bindings, flags: &[String], gecko: bool) {
    let suffix = if bindings.cplusplus { ".hpp" } else { ".h" };
    let header_path = PathBuf::from(BINDINGS_DIR).join(String::from(base) + suffix);
    let header = header_path.to_str().unwrap();
    let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join(String::from(base) + ".rs");

    println!("cargo:rerun-if-changed={header}");

    let mut builder = Builder::default().header(header);
    builder = builder.generate_comments(false);
    builder = builder.size_t_is_usize(true);

    builder = builder.clang_arg("-v");

    if !gecko {
        let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
        builder = builder.clang_arg("-DNO_NSPR_10_SUPPORT");
        if target_os == "windows" {
            builder = builder.clang_arg("-DWIN");
        } else if target_os == "macos" {
            builder = builder.clang_arg("-DDARWIN");
        } else if target_os == "linux" {
            builder = builder.clang_arg("-DLINUX");
        } else if target_os == "android" {
            builder = builder.clang_arg("-DLINUX");
            builder = builder.clang_arg("-DANDROID");
        }
        if bindings.cplusplus {
            builder = builder.clang_args(&["-x", "c++", "-std=c++14"]);
        }
    }

    builder = builder.clang_args(flags);

    // Apply the configuration.
    for v in &bindings.types {
        builder = builder.allowlist_type(v);
    }
    for v in &bindings.functions {
        builder = builder.allowlist_function(v);
    }
    for v in &bindings.variables {
        builder = builder.allowlist_var(v);
    }
    for v in &bindings.exclude {
        builder = builder.blocklist_item(v);
    }
    for v in &bindings.opaque {
        builder = builder.opaque_type(v);
    }
    for v in &bindings.enums {
        builder = builder.constified_enum_module(v);
    }

    let bindings = builder.generate().expect("unable to generate bindings");
    bindings
        .write_to_file(out)
        .expect("couldn't write bindings");
}

fn pkg_config() -> Result<Vec<String>, Box<dyn Error>> {
    let modversion = Command::new("pkg-config")
        .args(["--modversion", "nss"])
        .output()?
        .stdout;

    let modversion = String::from_utf8(modversion)?;

    let modversion = modversion.trim();

    // The NSS version number does not follow semver numbering, because it omits the patch version
    // when that's 0. Deal with that.
    let modversion_for_cmp = if modversion.chars().filter(|c| *c == '.').count() == 1 {
        modversion.to_owned() + ".0"
    } else {
        modversion.to_owned()
    };

    let modversion_for_cmp = Version::parse(&modversion_for_cmp)?;

    let version_req = VersionReq::parse(&format!(">={}", MINIMUM_NSS_VERSION.trim()))?;

    assert!(
        version_req.matches(&modversion_for_cmp),
        "nss-rs has NSS version requirement {version_req}, found {modversion}",
    );

    let cfg = Command::new("pkg-config")
        .args(["--cflags", "--libs", "nss"])
        .output()?
        .stdout;

    let cfg_str = String::from_utf8(cfg)?;

    let mut flags: Vec<String> = Vec::new();
    let mut lib_dirs: Vec<PathBuf> = Vec::new();

    for f in cfg_str.split_whitespace() {
        if f.starts_with("-I") {
            flags.push(String::from(f));
        } else if let Some(path) = f.strip_prefix("-L") {
            println!("cargo:rustc-link-search=native={path}");
            lib_dirs.push(PathBuf::from(path));
        } else if let Some(lib) = f.strip_prefix("-l") {
            println!("cargo:rustc-link-lib=dylib={lib}");
        } else {
            println!("cargo:warning=Unknown flag from pkg-config: {f}");
        }
    }

    if env::var("CARGO_FEATURE_BLAPI").is_ok() {
        // pkg-config omits -L for default system library paths (e.g., /usr/lib64 on
        // RHEL/Fedora), so also include the libdir from the .pc file.
        if let Ok(output) = Command::new("pkg-config")
            .args(["--variable=libdir", "nss"])
            .output()
            && output.status.success()
            && let Ok(s) = String::from_utf8(output.stdout)
        {
            let trimmed = s.trim();
            if !trimmed.is_empty() {
                let dir = PathBuf::from(trimmed);
                if !lib_dirs.contains(&dir) {
                    println!("cargo:rustc-link-search=native={}", dir.display());
                    lib_dirs.push(dir);
                }
            }
        }
    }
    maybe_link_freebl3();

    Ok(flags)
}

fn setup_standalone(nss_dir: String) -> Vec<String> {
    let nss = PathBuf::from(nss_dir);
    println!("cargo:rerun-if-env-changed=NSS_DIR");
    println!("cargo:rerun-if-env-changed=NSS_PREBUILT");

    // $NSS_DIR/../dist/
    let nssdist = nss.parent().unwrap().join("dist");
    let nsstarget = "Release";

    // If NSS_PREBUILT is set to a non-zero value, we assume that the NSS libraries are already
    // built.
    if !env::var("NSS_PREBUILT").is_ok_and(|v| v != "0") {
        build_nss(nss);
    }

    let nsstarget = nssdist.join(nsstarget);
    let includes = get_includes(&nsstarget, &nssdist);

    let nsslibdir = nsstarget.join("lib");
    println!(
        "cargo:rustc-link-search=native={}",
        nsslibdir.to_str().unwrap()
    );
    if env::var("CARGO_CFG_FUZZING").is_ok()
        || env::var("PROFILE").unwrap_or_default() == "debug"
        // FIXME: NSPR doesn't build proper dynamic libraries on Windows.
        || env::var("CARGO_CFG_TARGET_OS").unwrap() == "windows"
    {
        static_link();
    } else {
        dynamic_link();
    }

    let mut flags: Vec<String> = Vec::new();
    for i in includes {
        flags.push(String::from("-I") + i.to_str().unwrap());
    }

    flags
}

#[cfg(feature = "gecko")]
fn setup_for_gecko() -> Vec<String> {
    use mozbuild::{
        TOPOBJDIR,
        config::{BINDGEN_SYSTEM_FLAGS, NSPR_CFLAGS, NSS_CFLAGS},
    };

    let fold_libs = mozbuild::config::MOZ_FOLD_LIBS;
    let libs = if fold_libs {
        vec!["nss3"]
    } else {
        vec!["nssutil3", "nss3", "ssl3", "plds4", "plc4", "nspr4"]
    };

    for lib in &libs {
        println!("cargo:rustc-link-lib=dylib={}", lib);
    }

    maybe_link_freebl3_gecko(TOPOBJDIR, fold_libs);

    if fold_libs {
        println!(
            "cargo:rustc-link-search=native={}",
            TOPOBJDIR.join("security").display()
        );
    } else {
        println!(
            "cargo:rustc-link-search=native={}",
            TOPOBJDIR.join("dist").join("bin").display()
        );
        let nsslib_path = TOPOBJDIR.join("security").join("nss").join("lib");
        println!(
            "cargo:rustc-link-search=native={}",
            nsslib_path.join("nss").join("nss_nss3").display()
        );
        println!(
            "cargo:rustc-link-search=native={}",
            nsslib_path.join("ssl").join("ssl_ssl3").display()
        );
        println!(
            "cargo:rustc-link-search=native={}",
            TOPOBJDIR
                .join("config")
                .join("external")
                .join("nspr")
                .join("pr")
                .display()
        );
    }

    let mut flags = BINDGEN_SYSTEM_FLAGS
        .iter()
        .chain(&NSPR_CFLAGS)
        .chain(&NSS_CFLAGS)
        .map(|s| s.to_string())
        .collect::<Vec<_>>();

    flags.push(String::from("-include"));
    flags.push(
        TOPOBJDIR
            .join("dist")
            .join("include")
            .join("mozilla-config.h")
            .to_str()
            .unwrap()
            .to_string(),
    );
    flags
}

#[cfg(not(feature = "gecko"))]
fn setup_for_gecko() -> Vec<String> {
    unreachable!()
}

fn process_config(config: &mut HashMap<String, Bindings>) {
    let mut excludes = HashMap::new();
    for header in config.keys().cloned() {
        // Collect the list of types, functions, and variables configured
        // for generation in any other configured header, and add it to the list
        // of items excluded from generation in this header. This ensures that
        // each item only appears in one bindings module, which prevents some
        // type conflicts. (However, it does mean that appropriate `use`
        // declarations must be added for the generated modules.)
        excludes.insert(
            header.clone(),
            config
                .iter()
                .flat_map(|(h, b)| {
                    if *h == header {
                        vec![]
                    } else {
                        vec![&b.types, &b.functions, &b.variables]
                    }
                    .into_iter()
                    .flat_map(|v| v.iter())
                    .cloned()
                })
                .collect::<HashSet<String>>(),
        );
    }

    for (header, excludes) in excludes {
        config
            .get_mut(&header)
            .expect("key disappeared from config?")
            .exclude
            .extend(excludes);
    }
}

fn main() {
    println!("cargo:rerun-if-changed=src/min_version.rs");
    println!("cargo:rerun-if-changed=min_version.txt");
    println!("cargo:rustc-check-cfg=cfg(nss_nodb)");
    setup_clang();

    let flags = if cfg!(feature = "gecko") {
        setup_for_gecko()
    } else if let Ok(nss_dir) = env::var("NSS_DIR") {
        setup_standalone(nss_dir.trim().to_string())
    } else {
        pkg_config().unwrap_or_else(|_| setup_standalone(nss_dir()))
    };

    let config_file = PathBuf::from(BINDINGS_DIR).join(BINDINGS_CONFIG);
    println!("cargo:rerun-if-changed={}", config_file.to_str().unwrap());
    let config = fs::read_to_string(config_file).expect("unable to read binding configuration");
    let mut config: HashMap<String, Bindings> = ::toml::from_str(&config).unwrap();
    process_config(&mut config);

    for (k, v) in &config {
        build_bindings(k, v, &flags[..], cfg!(feature = "gecko"));
    }
}

[Verzeichnis aufwärts0.57unsichere Verbindung]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

letze Version des Elbe Quellennavigators


Jenseits des Üblichen ....
    

Besucher

Besucher

Statistik
#Sources=141584
#Domains=752002