1use regex::Regex;
2use std::path::Path;
3
4pub fn build_thirdparty(
8 name: &str,
9 target_dir: &Path,
10 profile: &str,
11 definitions: &[(&str, &str)],
12) -> String {
13 let out_dir = target_dir.join(&format!("build-{}", name));
16 match std::fs::create_dir(&out_dir) {
17 Ok(_) => (),
18 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => (),
19 Err(e) => panic!(
20 "Could not create build directory '{}': {}",
21 out_dir.display(),
22 e
23 ),
24 }
25
26 let mut config = cmake::Config::new(&format!("thirdparty/{}", name));
27 config.profile(profile);
28 config.define("CMAKE_INSTALL_PREFIX", target_dir.to_str().unwrap());
29 config.define("CMAKE_PREFIX_PATH", target_dir.join("lib").join("cmake"));
30 config.out_dir(&out_dir);
31
32 for def in definitions {
33 config.define(def.0, def.1);
34 }
35
36 config
37 .build()
38 .to_str()
39 .expect(&format!("Unable to convert {} dst to str", name))
40 .to_string()
41}
42
43#[derive(Debug)]
49pub struct DylibPathInfo {
50 pub path: String,
51 pub basename: String,
52 pub libname: String,
53}
54
55#[derive(Debug)]
56pub enum LinkArg {
57 LinkDir(String),
58 LinkLib(String),
59 Path(DylibPathInfo),
60}
61
62#[cfg(not(target_os = "windows"))]
63fn is_dylib_path(s: &str, re: &Regex) -> Option<LinkArg> {
64 if let Ok(_) = std::env::var("CPPMM_DEBUG_BUILD") {
65 println!("cargo:warning=- {}", s);
66 }
67
68 if let Some(pos @ 0) = s.find("-l") {
69 return Some(LinkArg::LinkLib(s[2..].to_string()))
70 } else if let Some(pos @ 0) = s.find("-L") {
71 if let Ok(_) = std::env::var("CPPMM_DEBUG_BUILD") {
72 println!("cargo:warning= is a link dir {}", s);
73 }
74 return Some(LinkArg::LinkDir(s[2..].to_string()))
75 } else if let Some(m) = re.captures_iter(s).next() {
76 if let Some(c0) = m.get(0) {
77 if let Some(c1) = m.get(1) {
78 if let Ok(_) = std::env::var("CPPMM_DEBUG_BUILD") {
79 println!("cargo:warning= is a dylib path {}", s);
80 }
81 return Some(LinkArg::Path(DylibPathInfo {
82 path: s.to_string(),
83 basename: c0.as_str().to_string(),
84 libname: c1.as_str().to_string(),
85 }));
86 }
87 }
88 }
89 if let Ok(_) = std::env::var("CPPMM_DEBUG_BUILD") {
90 println!("cargo:warning= is not a dylib path");
91 }
92
93 None
94}
95
96#[cfg(target_os = "windows")]
97fn is_dll_lib_path(s: &str, re: &Regex) -> Option<LinkArg> {
98 if let Some(m) = re.captures_iter(s).next() {
99 if let Some(c0) = m.get(0) {
100 if let Some(c1) = m.get(1) {
101 return Some(LinkArg::Path(DylibPathInfo {
102 path: s.to_string(),
103 basename: c0.as_str().to_string(),
104 libname: c1.as_str().to_string(),
105 }));
106 }
107 }
108 }
109
110 None
111}
112
113#[cfg(target_os = "windows")]
114fn get_linking_from_vsproj(
115 build_path: &Path,
116 clib_versioned_name: &str,
117 build_type: &str,
118) -> Option<Vec<LinkArg>> {
119 use quick_xml::events::{BytesEnd, BytesStart, Event};
120 use quick_xml::Reader;
121 use std::borrow::Borrow;
122 use std::io::Cursor;
123 use std::iter;
124
125 let proj_path = build_path.join(format!("{}.vcxproj", clib_versioned_name));
126 let proj_xml = std::fs::read_to_string(&proj_path).ok()?;
127
128 let re = Regex::new(r"(?:.*\\(.*))(\.lib)$").unwrap();
129
130 let mut reader = Reader::from_str(&proj_xml);
131 reader.trim_text(true);
132
133 let mut in_item_definition = false;
134 let mut in_link = false;
135 let mut in_deps = false;
136
137 let mut buf = Vec::new();
138
139 loop {
140 match reader.read_event(&mut buf) {
141 Ok(Event::Start(ref e)) => match e.name() {
142 b"ItemDefinitionGroup" => {
143 for attr in e.attributes() {
144 if let Ok(attr) = attr {
145 if attr.key == b"Condition" {
146 let s =
147 std::str::from_utf8(attr.value.borrow())
148 .unwrap();
149 if s.contains(build_type) {
150 in_item_definition = true;
151 }
152 }
153 }
154 }
155 }
156 b"Link" if in_item_definition => {
157 in_link = true;
158 }
159 b"AdditionalDependencies" if in_item_definition && in_link => {
160 in_deps = true;
161 }
162 _ => (),
163 },
164 Ok(Event::End(ref e)) => match e.name() {
165 b"ItemDefinitionGroup" => {
166 in_item_definition = false;
167 }
168 b"Link" => {
169 in_link = false;
170 }
171 b"AdditionalDependencies" => in_deps = false,
172 _ => (),
173 },
174 Ok(Event::Text(e)) if in_deps => {
175 let mut dlls = Vec::new();
176 for tok in e.unescape_and_decode(&reader).unwrap().split(";") {
177 if let Some(dll) = is_dll_lib_path(tok, &re) {
178 dlls.push(dll)
179 }
180 }
181 return Some(dlls);
182 }
183 Ok(Event::Eof) => break,
184 Err(e) => panic!("Error parsing vsproj xml"),
185 _ => (),
186 }
187 }
188
189 None
190}
191
192#[cfg(target_os = "windows")]
193fn get_linking_from_nmake(
194 build_path: &Path,
195 clib_versioned_name: &str,
196) -> Option<Vec<LinkArg>> {
197 let build_make_path = build_path
198 .join("CMakeFiles")
199 .join(format!("{}-shared.dir", clib_versioned_name))
200 .join("build.make");
201
202 let build_make = std::fs::read_to_string(&build_make_path).ok()?;
203
204 let re = Regex::new(r"(?:.*\\(.*))(\.lib)$").unwrap();
205
206 let mut found_slash_dll = false;
207 let mut libs = Vec::new();
208 for tok in build_make.split_whitespace() {
210 if tok == "/dll" {
211 found_slash_dll = true;
212 } else if found_slash_dll {
213 if tok == "<<" {
214 break;
215 } else {
216 if let Some(dlp) = is_dll_lib_path(tok, &re) {
217 libs.push(dlp);
218 }
219 }
220 }
221 }
222
223 Some(libs)
224}
225
226#[cfg(target_os = "windows")]
227pub fn get_linking_from_cmake(
234 build_path: &Path,
235 clib_versioned_name: &str,
236 build_type: &str,
237) -> Vec<LinkArg> {
238 if let Some(libs) =
239 get_linking_from_vsproj(build_path, clib_versioned_name, build_type)
240 {
241 libs
242 } else if let Some(libs) =
243 get_linking_from_nmake(build_path, clib_versioned_name)
244 {
245 libs
246 } else {
247 panic!("Could not open either vsproj or nmake build");
248 }
249}
250
251#[cfg(not(target_os = "windows"))]
252pub fn get_linking_from_cmake(
253 build_path: &Path,
254 clib_versioned_name: &str,
255 _build_type: &str,
256) -> Vec<LinkArg> {
257 let link_txt_path = build_path
258 .join("CMakeFiles")
259 .join(format!("{}.dir", clib_versioned_name))
260 .join("link.txt");
261 let link_txt = std::fs::read_to_string(&link_txt_path).expect(&format!(
262 "Could not read link_txt_path: {}",
263 link_txt_path.display()
264 ));
265
266 if let Ok(_) = std::env::var("CPPMM_DEBUG_BUILD") {
267 println!("cargo:warning=Reading link.txt {}", link_txt);
268 }
269
270 let re = Regex::new(
271 r"lib([^/]+?)(?:\.dylib|\.so|\.so.\d+|\.so.\d+.\d+|\.so.\d+.\d+.\d+)$",
272 )
273 .unwrap();
274
275 let mut link_txt = link_txt.split(' ');
280 while let Some(s) = link_txt.next() {
281 if s == "-o" {
282 let _ = link_txt.next();
284 break;
285 }
286 }
287
288 link_txt.filter_map(|s| is_dylib_path(s, &re)).collect()
291}
292
293pub struct Dependency {
294 pub name: &'static str,
295 pub definitions: Vec<(&'static str, &'static str)>,
296}
297
298use std::fmt;
299impl fmt::Debug for Dependency {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 write!(f, "{}", self.name)
302 }
303}
304
305pub fn build(project_name: &str, major_version: u32, minor_version: u32, dependencies: &[Dependency]) {
326
327 let env_build_libraries = format!("CPPMM_{}_BUILD_LIBRARIES", project_name.to_ascii_uppercase());
328 let env_build_type = format!("CPPMM_{}_BUILD_TYPE", project_name.to_ascii_uppercase());
329
330 let build_libraries = if std::env::var("CMAKE_PREFIX_PATH").is_ok() {
333 if let Ok(obl) = std::env::var(&env_build_libraries) {
334 obl == "1"
335 } else {
336 false
337 }
338 } else {
339 true
340 };
341
342 let out_dir = std::env::var("OUT_DIR").unwrap();
343 let target_dir = Path::new(&out_dir).ancestors().skip(3).next().unwrap();
344
345 let clib_name = format!("{}-c", project_name);
346 let clib_versioned_name =
347 format!("{}-c-{}_{}", project_name, major_version, minor_version);
348 let clib_shared_versioned_name =
349 format!("{}-c-{}_{}-shared", project_name, major_version, minor_version);
350
351 let lib_path = target_dir.join("lib");
352 let bin_path = target_dir.join("bin");
353 let cmake_prefix_path = lib_path.join("cmake");
354
355 let build_type =
357 if let Ok(build_type) = std::env::var(&env_build_type) {
358 build_type
359 } else {
360 "Release".to_string()
361 };
362
363 let dst = if build_libraries {
364 println!("cargo:warning=Building packaged dependencies {:?}", dependencies);
365 for dep in dependencies {
366 build_thirdparty(dep.name, target_dir, &build_type, &dep.definitions);
367 }
368
369 cmake::Config::new(clib_name)
370 .define("CMAKE_EXPORT_COMPILE_COMMANDS", "ON")
371 .define("CMAKE_PREFIX_PATH", cmake_prefix_path.to_str().unwrap())
372 .profile(&build_type)
373 .build()
374 } else {
375 println!("cargo:warning=Using system dependencies {:?}", dependencies);
376 cmake::Config::new(clib_name)
377 .define("CMAKE_EXPORT_COMPILE_COMMANDS", "ON")
378 .profile(&build_type)
379 .build()
380 };
381
382 let build_path = Path::new(&dst).join("build");
383
384 let link_args = get_linking_from_cmake(
385 &build_path,
386 &clib_shared_versioned_name,
387 &build_type,
388 );
389 println!("cargo:warning=Link libs: {:?}", link_args);
390
391 println!("cargo:rustc-link-search=native={}", dst.display());
420 #[cfg(not(target_os = "windows"))]
421 println!("cargo:rustc-link-lib=static={}", clib_versioned_name);
422 #[cfg(target_os = "windows")]
423 println!("cargo:rustc-link-lib=dylib={}", clib_shared_versioned_name);
424
425 if build_libraries {
426 println!("cargo:rustc-link-search=native={}", lib_path.display());
428 println!("cargo:rustc-link-search=native={}", bin_path.display());
432 }
433
434 for arg in link_args {
435 match arg {
437 LinkArg::Path(d) => {
438 let libdir = Path::new(&d.path).parent().unwrap();
439 println!("cargo:rustc-link-search=native={}", libdir.display());
440 println!("cargo:rustc-link-lib=dylib={}", &d.libname);
441 }
442 LinkArg::LinkDir(dir) => {
443 println!("cargo:rustc-link-search=native={}", dir);
444 }
445 LinkArg::LinkLib(lib) => {
446 println!("cargo:rustc-link-lib=dylib={}", lib);
447 }
448 }
449 }
450
451 #[cfg(target_os = "linux")]
453 println!("cargo:rustc-link-lib=dylib=stdc++");
454 #[cfg(target_os = "macos")]
455 println!("cargo:rustc-link-lib=dylib=c++");
456
457 let build_dir = Path::new(&out_dir).join("build");
471 let abigen_bin = build_dir.join("abigen").join("abigen");
472 let abigen_txt = build_dir.join("abigen.txt");
473
474 if !abigen_txt.exists() {
476 let _ = std::process::Command::new(abigen_bin)
477 .current_dir(build_dir)
478 .output()
479 .expect("Could not run abigen");
480 }
481
482 let cppmm_abi_out = Path::new(&out_dir).join("cppmm_abi_out").join("cppmmabi.rs");
483
484 if !cppmm_abi_out.exists() {
486 let output = std::process::Command::new("python")
487 .args(&[&format!("{}-c/abigen/insert_abi.py", project_name),
488 "cppmm_abi_in",
489 &format!("{}/cppmm_abi_out", out_dir),
490 &format!("{}/build/abigen.txt", out_dir)])
491 .output()
492 .expect("Could not launch python insert_abi.py");
493
494 if !output.status.success() {{
495 for line in std::str::from_utf8(&output.stderr).unwrap().lines() {{
496 println!("cargo:warning={}", line);
497 }}
498 panic!("python insert_abi failed");
499 }}
500 }
501
502}
503
504#[cfg(test)]
505mod tests {
506 #[test]
507 fn it_works() {
508 assert_eq!(2 + 2, 4);
509 }
510}