1use anyhow::Context;
5use std::{
6 env,
7 ffi::{self, OsString},
8 path::{Path, PathBuf},
9};
10
11pub use cc;
13
14#[derive(Clone, Debug)]
15pub enum LinkableTarget {
16 Path(PathBuf),
17 Name(String),
18}
19
20#[derive(Clone, Debug)]
21pub enum Linkable {
22 Static(LinkableTarget),
23 Dynamic(LinkableTarget),
24}
25
26#[derive(Clone, Debug)]
27enum OutputType {
28 Executable,
29 Shared,
30}
31
32#[derive(Clone, Debug)]
33pub struct ImprovedBuild {
34 files: Vec<PathBuf>,
35 linkables: Vec<Linkable>,
36 cc_build: cc::Build,
37 emit_rerun_if_env_changed: bool,
38}
39
40impl Linkable {
41 fn concat_os_strings(a: &ffi::OsStr, b: &ffi::OsStr) -> OsString {
42 let mut ret = OsString::with_capacity(a.len() + b.len());
43 ret.push(a);
44 ret.push(b);
45 ret
46 }
47
48 pub fn to_compiler_args(&self, _compiler: &cc::Tool) -> Vec<OsString> {
49 match self {
52 Linkable::Static(target) => match target {
53 LinkableTarget::Path(p) => vec![p.as_os_str().to_owned()],
54 LinkableTarget::Name(name) => vec![format!("-l{name}").into()],
55 },
56 Linkable::Dynamic(target) => match target {
57 LinkableTarget::Path(path) => {
58 let mut args = vec![];
59
60 if let Some(dirname) = path.parent() {
61 args.push(Self::concat_os_strings(
62 ffi::OsStr::new("-L"),
63 dirname.as_os_str(),
64 ))
65 }
66 if let Some(filename) = path.file_name() {
67 args.push(Self::concat_os_strings(ffi::OsStr::new("-l"), filename))
68 }
69 args
70 }
71 LinkableTarget::Name(name) => vec![format!("-l{name}").into()],
72 },
73 }
74 }
75}
76
77impl ImprovedBuild {
78 pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut Self {
79 self.files.push(p.as_ref().to_path_buf());
80 self
81 }
82
83 pub fn files<P>(&mut self, p: P) -> &mut Self
84 where
85 P: IntoIterator,
86 P::Item: AsRef<Path>,
87 {
88 for file in p.into_iter() {
89 self.file(file);
90 }
91 self
92 }
93
94 pub fn link_dynamically<S: AsRef<str>>(&mut self, name: S) -> &mut Self {
95 self.linkables.push(Linkable::Dynamic(LinkableTarget::Name(
96 name.as_ref().to_owned(),
97 )));
98 self
99 }
100
101 pub fn set_cc_builder(&mut self, cc_build: cc::Build) -> &mut Self {
102 self.cc_build = cc_build;
103 self
104 }
105
106 pub fn emit_rerun_if_env_changed(&mut self, emit: bool) -> &mut Self {
107 self.emit_rerun_if_env_changed = emit;
108 self.cc_build.emit_rerun_if_env_changed(emit);
109 self
110 }
111
112 pub fn new() -> Self {
113 let cc_build = cc::Build::new();
114
115 ImprovedBuild {
116 files: Default::default(),
117 linkables: Default::default(),
118 cc_build,
119 emit_rerun_if_env_changed: false,
120 }
121 }
122
123 fn get_out_dir(&self) -> anyhow::Result<PathBuf> {
124 env::var_os("OUT_DIR")
125 .map(PathBuf::from)
126 .context("can't get output directory info")
127 }
128
129 pub fn define<'a, V: Into<Option<&'a str>>>(&mut self, var: &str, val: V) -> &mut Self {
131 self.cc_build.define(var, val);
132 self
133 }
134
135 pub fn flag(&mut self, flag: &str) -> &mut Self {
136 self.cc_build.flag(flag);
137 self
138 }
139
140 pub fn warnings(&mut self, warnings: bool) -> &mut Self {
141 self.cc_build.warnings(warnings);
142 self
143 }
144
145 pub fn warnings_into_errors(&mut self, warnings_into_errors: bool) -> &mut Self {
146 self.cc_build.warnings_into_errors(warnings_into_errors);
147 self
148 }
149
150 pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut Self {
151 self.cc_build.include(dir);
152 self
153 }
154
155 pub fn includes<P>(&mut self, dirs: P) -> &mut Self
156 where
157 P: IntoIterator,
158 P::Item: AsRef<Path>,
159 {
160 self.cc_build.includes(dirs);
161 self
162 }
163
164 pub fn try_compile_executable(&self, output: &str) -> anyhow::Result<()> {
165 self.try_compile_any(output, OutputType::Executable)
166 }
167
168 pub fn try_compile_shared_lib(&self, output: &str) -> anyhow::Result<()> {
169 self.try_compile_any(output, OutputType::Shared)
170 }
171
172 pub fn cpp(&mut self, is_cpp: bool) -> &mut Self {
173 self.cc_build.cpp(is_cpp);
174 self
175 }
176
177 fn try_compile_any(&self, output: &str, output_type: OutputType) -> anyhow::Result<()> {
178 if self.emit_rerun_if_env_changed {
179 for file in self.files.iter() {
180 println!(
181 "cargo:rerun-if-changed={}",
182 file.as_path().to_string_lossy()
183 );
184 }
185 }
186
187 let compiler = self.cc_build.try_get_compiler()?;
188 let output_path = self
189 .get_out_dir()
190 .map_or(output.into(), |path| path.join(output));
191
192 let mut cmd = compiler.to_command();
193
194 match output_type {
195 OutputType::Executable => {
196 cmd.args(["-o".into(), output_path.as_os_str().to_owned()]);
197 }
198 OutputType::Shared => {
199 cmd.args([
200 "-shared".into(),
201 "-o".into(),
202 output_path.as_os_str().to_owned(),
203 ]);
204 }
205 }
206
207 for file in &self.files {
208 cmd.arg(file.as_os_str());
209 }
210
211 for linkable in &self.linkables {
212 cmd.args(linkable.to_compiler_args(&compiler));
213 }
214 println!("compiling: {cmd:?}");
215 let status = cmd.spawn()?.wait()?;
216
217 if !status.success() {
218 return Err(anyhow::format_err!("compilation failed"));
219 }
220
221 Ok(())
222 }
223}
224
225impl Default for ImprovedBuild {
226 fn default() -> Self {
227 Self::new()
228 }
229}