-
Notifications
You must be signed in to change notification settings - Fork 3
/
build.rs
159 lines (126 loc) · 5.04 KB
/
build.rs
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
use anyhow::{anyhow, Context};
use relative_path::RelativePathBuf;
use std::{
env,
fmt::Display,
fs,
path::{Path, PathBuf},
process::Command,
};
#[cfg(feature = "deny-net-fetch")]
use anyhow::bail;
// update this whenever you change the subtree pointer
const CAPNP_VERSION: &str = "0.11.0";
enum CapnprotoAcquired {
Locally(relative_path::RelativePathBuf),
OnSystem(PathBuf),
}
impl Display for CapnprotoAcquired {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CapnprotoAcquired::Locally(e) => write!(f, "{}", e),
CapnprotoAcquired::OnSystem(e) => write!(f, "{}", e.display()),
}
}
}
fn main() -> anyhow::Result<()> {
// we're making the assumption that the executable is always accessible.
// if we can't make this assumption, we can just include_bytes!() it and then unpack it at runtime.
println!("cargo:rerun-if-changed=capnproto");
let out_dir = PathBuf::from(
env::var("OUT_DIR").context("Cargo did not set $OUT_DIR. this should be impossible.")?,
);
// updated with the final path of the capnp binary if it's ever found, to be recorded
// and consumed by capnp_import!()
let mut capnp_path: Option<CapnprotoAcquired> = None;
// only build if it can't be detected in the $PATH
// check if there is a capnp binary in the path that meets the version requirement
let existing_capnp: anyhow::Result<PathBuf> = (|| {
let bin = which::which("capnp").context("could not find a system capnp binary")?;
let version = get_version(&bin).context(
"could not obtain version of found binary, system capnp may be inaccessible",
)?;
println!("found capnp '{version}'");
if version.trim() == format!("Cap'n Proto version {}", CAPNP_VERSION) {
capnp_path = Some(CapnprotoAcquired::OnSystem(bin.clone()));
Ok(bin)
} else {
println!("cargo:warning=System version of capnp found ({}) does not meet version requirement {CAPNP_VERSION}.", &version);
Err(anyhow!(
"version of system capnp does not meet version requirements"
))?
}
})();
// no capnp here, proceed to build
if let Err(e) = existing_capnp {
#[cfg(feature = "deny-net-fetch")]
bail!("Couldn't find a local capnp: {}\n refusing to build", e);
println!("Couldn't find a local capnp: {}", e);
println!("building...");
// when capnproto accepts our PR, windows can fetch bin artifacts from it.
// until then, we must build capnproto ourselves.
let built_bin = build_with_cmake(&out_dir)?;
capnp_path = Some(built_bin);
}
fs::write(
out_dir.join("extract_bin.rs"),
format!(
"
#[allow(dead_code)]
fn commandhandle() -> anyhow::Result<tempfile::TempDir> {{
use std::io::Write;
#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]
use std::os::unix::fs::OpenOptionsExt;
use tempfile::tempdir;
let file_contents = include_bytes!(\"{}/{}\");
let tempdir = tempdir()?;
#[cfg(any(target_os = \"linux\", target_os = \"macos\"))]
let mut handle =
std::fs::OpenOptions::new()
.write(true)
.mode(0o770)
.create(true)
.open(tempdir.path().join(\"capnp\"))?;
#[cfg(target_os = \"windows\")]
let mut handle = std::fs::OpenOptions::new().write(true).create(true).open(tempdir.path().join(\"capnp\"))?;
#[cfg(not(any(target_os = \"linux\", target_os = \"macos\", target_os = \"windows\")))]
compile_error!(\"capnp-import does not support your operating system!\");
handle.write_all(file_contents)?;
Ok(tempdir)
}}",
out_dir.to_string_lossy().replace('\\', "/"),
capnp_path.unwrap(),
),
)?;
Ok(())
}
fn get_version(executable: &Path) -> anyhow::Result<String> {
let version = String::from_utf8(Command::new(executable).arg("--version").output()?.stdout)?;
Ok(version)
}
// build capnproto with cmake, configured for windows and linux envs
fn build_with_cmake(out_dir: &PathBuf) -> anyhow::Result<CapnprotoAcquired> {
// is dst consistent? might need to write this down somewhere if it isn't
let mut dst = cmake::Config::new("capnproto");
if which::which("ninja").is_ok() {
dst.generator("Ninja");
}
// it would be nice to be able to use mold
#[cfg(target_os = "windows")]
dst.cxxflag("/EHsc");
let dst = dst.define("BUILD_TESTING", "OFF").build();
assert_eq!(*out_dir, dst);
// place the capnproto binary in $OUT_DIR, next to where binary_decision.rs
// is intended to go
if cfg!(target_os = "windows") {
Ok(CapnprotoAcquired::Locally(RelativePathBuf::from(
"bin/capnp.exe",
)))
} else if cfg!(target_os = "linux") || cfg!(target_os = "macos") {
Ok(CapnprotoAcquired::Locally(RelativePathBuf::from(
"bin/capnp",
)))
} else {
panic!("Sorry, capnp-import does not support your operating system.");
}
}