cabal_foreign_library/
lib.rs1mod error;
13mod util;
14
15use std::process::Command;
16use std::{fs, str};
17
18use camino::{Utf8Path, Utf8PathBuf};
19use regex::Regex;
20
21use util::{out_dir, package, CommandStdoutExt, DYLIB_EXT};
22
23pub use bindgen;
24pub use error::*;
25
26#[derive(Debug)]
28pub struct Build {
29 cabal: Utf8PathBuf,
30 ghc_pkg: Utf8PathBuf,
31 rts_version: RTSVersion,
32}
33
34#[derive(Debug)]
36pub struct Lib<'b> {
37 build: &'b Build,
38 path: Utf8PathBuf,
39 hs_deps: Vec<HSDep>,
40}
41
42#[derive(Debug, Clone, Copy)]
43enum HSDep {
44 Ghc,
45 Base,
46}
47
48pub type BindgenBuilder = bindgen::Builder;
50
51pub type Result<T> = std::result::Result<T, Error>;
53
54#[derive(Debug, Clone, Copy)]
56pub enum RTSVersion {
57 NonThreaded,
58 NonThreadedL,
59 NonThreadedDebug,
60 Threaded,
61 ThreadedL,
62 ThreadedDebug,
63}
64
65impl RTSVersion {
66 fn default() -> Self {
67 Self::NonThreaded
68 }
69}
70
71impl Build {
72 pub fn new() -> Result<Self> {
74 let cabal = util::which("cabal").map_err(Error::CabalError)?;
75 let ghc_pkg = util::which("ghc-pkg").map_err(Error::GHCPkgError)?;
76 Ok(Self {
77 cabal,
78 ghc_pkg,
79 rts_version: RTSVersion::default(),
80 })
81 }
82
83 pub fn use_cabal(&mut self, path: impl AsRef<Utf8Path>) -> &mut Self {
87 self.cabal = path.as_ref().to_path_buf();
88 self
89 }
90
91 pub fn use_ghc_pkg(&mut self, path: impl AsRef<Utf8Path>) -> &mut Self {
95 self.ghc_pkg = path.as_ref().to_path_buf();
96 self
97 }
98
99 pub fn use_rts(&mut self, rts_version: RTSVersion) -> &mut Self {
103 self.rts_version = rts_version;
104 self
105 }
106
107 pub fn build(&mut self) -> Result<Lib> {
110 let status = self
112 .cabal_cmd("build")
113 .status()
114 .map_err(|err| Error::BuildError(Some(err)))?;
115 if !status.success() {
116 return Err(Error::BuildError(None));
117 }
118
119 let path = self
121 .cabal_cmd("list-bin")
122 .arg(util::package())
123 .stdout_trim()
124 .map(Utf8PathBuf::from)
125 .map_err(|err| Error::BuildError(Some(err)))?;
126
127 Ok(Lib {
128 build: self,
129 path,
130 hs_deps: vec![HSDep::Ghc, HSDep::Base],
133 })
134 }
135
136 fn cabal_cmd(&self, cmd: &str) -> Command {
137 let mut cabal = Command::new(&self.cabal);
138 cabal.args([cmd, "--builddir", &out_dir()]);
139 cabal
140 }
141
142 fn ghc_pkg_cmd(&self, cmd: &str) -> Command {
143 let mut ghc_pkg = Command::new(&self.ghc_pkg);
144 ghc_pkg.args([cmd]);
145 ghc_pkg
146 }
147}
148
149impl<'b> Lib<'b> {
150 pub fn link(&self, rpath: bool) -> Result<()> {
155 let dir = self.path.parent().unwrap();
156 println!("cargo:rustc-link-search=native={}", dir);
157 println!("cargo:rustc-link-lib=dylib={}", &package());
158
159 if rpath {
160 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", dir);
161 }
162
163 Ok(())
164 }
165
166 pub fn bindings(&self) -> Result<bindgen::Builder> {
172 let rts_headers = self
174 .build
175 .ghc_pkg_cmd("field")
176 .args(["rts", "include-dirs", "--simple-output"])
177 .stdout_trim()
178 .map(Utf8PathBuf::from)
179 .map_err(InvocationError::IoError)
180 .map_err(Error::GHCPkgError)?;
181
182 let stub = self
184 .path
185 .parent()
186 .unwrap()
187 .join(format!("{}-tmp", package()))
188 .join("Lib_stub.h");
189
190 let builder = bindgen::Builder::default()
192 .clang_args(["-isystem", rts_headers.as_str()])
193 .header(stub)
194 .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
195
196 Ok(builder)
197 }
198
199 pub fn link_system(&self, rpath: bool) -> Result<()> {
204 let ghc_lib_dir = self
206 .build
207 .ghc_pkg_cmd("field")
208 .args(["rts", "dynamic-library-dirs", "--simple-output"])
209 .stdout_trim()
210 .map_err(InvocationError::IoError)
211 .map_err(Error::GHCPkgError)?;
212 let ghc_lib_dir = fs::canonicalize(ghc_lib_dir).unwrap();
213
214 let version_regex = Regex::new(r"((\d+)\.)+?(\d+)").unwrap();
216
217 let non_rts_prefixes = self
218 .hs_deps
219 .iter()
220 .map(HSDep::prefix)
221 .collect::<Vec<_>>()
222 .join("|");
223 let non_rts_regex = Regex::new(&format!(
224 r"^lib({prefix})-({version})-ghc({version})\.{ext}$",
225 prefix = non_rts_prefixes,
226 version = version_regex,
227 ext = DYLIB_EXT
228 ))
229 .unwrap();
230
231 let rts_suffix = self.build.rts_version.suffix();
232 let rts_regex = Regex::new(&format!(
233 r"^libHSrts-({version})({suffix})-ghc({version})\.{ext}$",
234 version = version_regex,
235 suffix = rts_suffix,
236 ext = DYLIB_EXT
237 ))
238 .unwrap();
239
240 println!("cargo:rustc-link-search=native={}", ghc_lib_dir.display());
242 for entry in fs::read_dir(&ghc_lib_dir).unwrap() {
243 let entry = entry.unwrap();
244
245 if let Some(i) = entry.file_name().to_str() {
246 if non_rts_regex.is_match(i) || rts_regex.is_match(i) {
247 let temp = i.split_at(3).1;
249 let trimmed = temp.split_at(temp.len() - DYLIB_EXT.len() - 1).0;
251
252 println!("cargo:rustc-link-lib=dylib={}", trimmed);
253 }
254 }
255 }
256
257 if rpath {
258 println!("cargo:rustc-link-arg=-Wl,-rpath,{}", ghc_lib_dir.display());
259 }
260
261 Ok(())
263 }
264}
265
266impl RTSVersion {
267 fn suffix(&self) -> &str {
268 match self {
269 RTSVersion::NonThreaded => "",
270 RTSVersion::NonThreadedL => "_l",
271 RTSVersion::NonThreadedDebug => "_debug",
272 RTSVersion::Threaded => "_thr",
273 RTSVersion::ThreadedL => "_thr_l",
274 RTSVersion::ThreadedDebug => "_thr_debug",
275 }
276 }
277}
278
279impl HSDep {
280 fn prefix(&self) -> &str {
281 match self {
282 HSDep::Ghc => "HSghc",
283 HSDep::Base => "HSbase",
284 }
285 }
286}