summaryrefslogtreecommitdiff
path: root/src/rust/cryptography-cffi/build.rs
blob: 4a40990b9da47ed1bfafdc1bebd75d0534dc2149 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// This file is dual licensed under the terms of the Apache License, Version
// 2.0, and the BSD License. See the LICENSE file in the root of this repository
// for complete details.

use std::env;
use std::path::Path;
use std::process::Command;

fn main() {
    let target = env::var("TARGET").unwrap();
    let openssl_static = env::var("OPENSSL_STATIC")
        .map(|x| x == "1")
        .unwrap_or(false);
    if target.contains("apple") && openssl_static {
        // On (older) OSX we need to link against the clang runtime,
        // which is hidden in some non-default path.
        //
        // More details at https://github.com/alexcrichton/curl-rust/issues/279.
        if let Some(path) = macos_link_search_path() {
            println!("cargo:rustc-link-lib=clang_rt.osx");
            println!("cargo:rustc-link-search={}", path);
        }
    }

    let out_dir = env::var("OUT_DIR").unwrap();
    // FIXME: maybe pyo3-build-config should provide a way to do this?
    let python = env::var("PYO3_PYTHON").unwrap_or_else(|_| "python3".to_string());
    println!("cargo:rerun-if-env-changed=PYO3_PYTHON");
    println!("cargo:rerun-if-changed=../../_cffi_src/");
    let output = Command::new(&python)
        .env("OUT_DIR", &out_dir)
        .arg("../../_cffi_src/build_openssl.py")
        .output()
        .expect("failed to execute build_openssl.py");
    if !output.status.success() {
        panic!(
            "failed to run build_openssl.py, stdout: \n{}\nstderr: \n{}\n",
            String::from_utf8(output.stdout).unwrap(),
            String::from_utf8(output.stderr).unwrap()
        );
    }

    let python_impl = run_python_script(
        &python,
        "import platform; print(platform.python_implementation(), end='')",
    )
    .unwrap();
    println!("cargo:rustc-cfg=python_implementation=\"{}\"", python_impl);
    let python_include = run_python_script(
        &python,
        "import sysconfig; print(sysconfig.get_path('include'), end='')",
    )
    .unwrap();
    let openssl_include =
        std::env::var_os("DEP_OPENSSL_INCLUDE").expect("unable to find openssl include path");
    let openssl_c = Path::new(&out_dir).join("_openssl.c");

    let mut build = cc::Build::new();
    build
        .file(openssl_c)
        .include(python_include)
        .include(openssl_include)
        .flag_if_supported("-Wconversion")
        .flag_if_supported("-Wno-error=sign-conversion")
        .flag_if_supported("-Wno-unused-parameter");

    // Enable abi3 mode if we're not using PyPy.
    if python_impl != "PyPy" {
        // cp37 (Python 3.7 to help our grep when we some day drop 3.7 support)
        build.define("Py_LIMITED_API", "0x030700f0");
    }

    if cfg!(windows) {
        build.define("WIN32_LEAN_AND_MEAN", None);
    }

    build.compile("_openssl.a");
}

/// Run a python script using the specified interpreter binary.
fn run_python_script(interpreter: impl AsRef<Path>, script: &str) -> Result<String, String> {
    let interpreter = interpreter.as_ref();
    let out = Command::new(interpreter)
        .env("PYTHONIOENCODING", "utf-8")
        .arg("-c")
        .arg(script)
        .output();

    match out {
        Err(err) => Err(format!(
            "failed to run the Python interpreter at {}: {}",
            interpreter.display(),
            err
        )),
        Ok(ok) if !ok.status.success() => Err(format!(
            "Python script failed: {}",
            String::from_utf8(ok.stderr).expect("failed to parse Python script stderr as utf-8")
        )),
        Ok(ok) => Ok(
            String::from_utf8(ok.stdout).expect("failed to parse Python script stdout as utf-8")
        ),
    }
}

fn macos_link_search_path() -> Option<String> {
    let output = Command::new("clang")
        .arg("--print-search-dirs")
        .output()
        .ok()?;
    if !output.status.success() {
        println!(
            "failed to run 'clang --print-search-dirs', continuing without a link search path"
        );
        return None;
    }

    let stdout = String::from_utf8_lossy(&output.stdout);
    for line in stdout.lines() {
        if line.contains("libraries: =") {
            let path = line.split('=').nth(1)?;
            return Some(format!("{}/lib/darwin", path));
        }
    }

    println!("failed to determine link search path, continuing without it");
    None
}