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