aboutsummaryrefslogtreecommitdiff
path: root/src/rust/build.rs
blob: 5626b35f751f1de5f7e2e086468c4dd157582e27 (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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
//! Build script for Rust modules in Tor.
//!
//! We need to use this because some of our Rust tests need to use some
//! of our C modules, which need to link some external libraries.
//!
//! This script works by looking at a "config.rust" file generated by our
//! configure script, and then building a set of options for cargo to pass to
//! the compiler.

use std::collections::HashMap;
use std::env;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::PathBuf;

/// Wrapper around a key-value map.
struct Config(HashMap<String, String>);

/// Locate a config.rust file generated by autoconf, starting in the OUT_DIR
/// location provided by cargo and recursing up the directory tree.  Note that
/// we need to look in the OUT_DIR, since autoconf will place generated files
/// in the build directory.
fn find_cfg() -> io::Result<String> {
    let mut path = PathBuf::from(env::var("OUT_DIR").unwrap());
    loop {
        path.push("config.rust");
        if path.exists() {
            return Ok(path.to_str().unwrap().to_owned());
        }
        path.pop(); // remove config.rust
        if !path.pop() {
            // can't remove last part of directory
            return Err(io::Error::new(io::ErrorKind::NotFound, "No config.rust"));
        }
    }
}

impl Config {
    /// Find the config.rust file and try to parse it.
    ///
    /// The file format is a series of lines of the form KEY=VAL, with
    /// any blank lines and lines starting with # ignored.
    fn load() -> io::Result<Config> {
        let path = find_cfg()?;
        let f = File::open(&path)?;
        let reader = io::BufReader::new(f);
        let mut map = HashMap::new();
        for line in reader.lines() {
            let s = line?;
            if s.trim().starts_with("#") || s.trim() == "" {
                continue;
            }
            let idx = match s.find("=") {
                None => {
                    return Err(io::Error::new(io::ErrorKind::InvalidData, "missing ="));
                }
                Some(x) => x,
            };
            let (var, eq_val) = s.split_at(idx);
            let val = &eq_val[1..];
            map.insert(var.to_owned(), val.to_owned());
        }
        Ok(Config(map))
    }

    /// Return a reference to the value whose key is 'key'.
    ///
    /// Panics if 'key' is not found in the configuration.
    fn get(&self, key: &str) -> &str {
        self.0.get(key).unwrap()
    }

    /// Add a dependency on a static C library that is part of Tor, by name.
    fn component(&self, s: &str) {
        println!("cargo:rustc-link-lib=static={}", s);
    }

    /// Add a dependency on a native library that is not part of Tor, by name.
    fn dependency(&self, s: &str) {
        println!("cargo:rustc-link-lib={}", s);
    }

    /// Add a link path, relative to Tor's build directory.
    fn link_relpath(&self, s: &str) {
        let builddir = self.get("BUILDDIR");
        println!("cargo:rustc-link-search=native={}/{}", builddir, s);
    }

    /// Add an absolute link path.
    fn link_path(&self, s: &str) {
        println!("cargo:rustc-link-search=native={}", s);
    }

    /// Parse the CFLAGS in s, looking for -l and -L items, and adding
    /// rust configuration as appropriate.
    fn from_cflags(&self, s: &str) {
        let mut next_is_lib = false;
        let mut next_is_path = false;
        for ent in self.get(s).split_whitespace() {
            if next_is_lib {
                self.dependency(ent);
                next_is_lib = false;
            } else if next_is_path {
                self.link_path(ent);
                next_is_path = false;
            } else if ent == "-l" {
                next_is_lib = true;
            } else if ent == "-L" {
                next_is_path = true;
            } else if ent.starts_with("-L") {
                self.link_path(&ent[2..]);
            } else if ent.starts_with("-l") {
                self.dependency(&ent[2..]);
            }
        }
    }
}

pub fn main() {
    let cfg = Config::load().unwrap();
    let package = env::var("CARGO_PKG_NAME").unwrap();

    match package.as_ref() {
        "crypto" => {
            // Right now, I'm having a separate configuration for each Rust
            // package, since I'm hoping we can trim them down.  Once we have a
            // second Rust package that needs to use this build script, let's
            // extract some of this stuff into a module.
            //
            // This is a ridiculous amount of code to be pulling in just
            // to test our crypto library: modularity would be our
            // friend here.
            cfg.from_cflags("TOR_LDFLAGS_zlib");
            cfg.from_cflags("TOR_LDFLAGS_openssl");
            cfg.from_cflags("TOR_LDFLAGS_libevent");

            cfg.link_relpath("src/lib");
            cfg.link_relpath("src/ext/keccak-tiny");
            cfg.link_relpath("src/ext/ed25519/ref10");
            cfg.link_relpath("src/ext/ed25519/donna");
            cfg.link_relpath("src/trunnel");

            // Note that we can't pull in "libtor-testing", or else we
            // will have dependencies on all the other rust packages that
            // tor uses.  We must be careful with factoring and dependencies
            // moving forward!
            cfg.component("tor-crypt-ops-testing");
            cfg.component("tor-sandbox-testing");
            cfg.component("tor-encoding-testing");
            cfg.component("tor-fs-testing");
            cfg.component("tor-net-testing");
            cfg.component("tor-buf-testing");
            cfg.component("tor-time-testing");
            cfg.component("tor-thread-testing");
            cfg.component("tor-memarea-testing");
            cfg.component("tor-log-testing");
            cfg.component("tor-lock-testing");
            cfg.component("tor-fdio-testing");
            cfg.component("tor-container-testing");
            cfg.component("tor-smartlist-core-testing");
            cfg.component("tor-string-testing");
            cfg.component("tor-malloc");
            cfg.component("tor-wallclock");
            cfg.component("tor-err-testing");
            cfg.component("tor-version-testing");
            cfg.component("tor-intmath-testing");
            cfg.component("tor-ctime-testing");
            cfg.component("curve25519_donna");
            cfg.component("keccak-tiny");
            cfg.component("ed25519_ref10");
            cfg.component("ed25519_donna");
            cfg.component("or-trunnel-testing");

            cfg.from_cflags("TOR_ZLIB_LIBS");
            cfg.from_cflags("TOR_LIB_MATH");
            cfg.from_cflags("NSS_LIBS");
            cfg.from_cflags("TOR_OPENSSL_LIBS");
            cfg.from_cflags("TOR_LIBEVENT_LIBS");
            cfg.from_cflags("TOR_LIB_WS32");
            cfg.from_cflags("TOR_LIB_GDI");
            cfg.from_cflags("TOR_LIB_USERENV");
            cfg.from_cflags("CURVE25519_LIBS");
            cfg.from_cflags("TOR_LZMA_LIBS");
            cfg.from_cflags("TOR_ZSTD_LIBS");
            cfg.from_cflags("LIBS");
        }
        _ => {
            panic!("No configuration in build.rs for package {}", package);
        }
    }
}