libdd_common/
cc_utils.rs

1// Copyright 2021-Present Datadog, Inc. https://www.datadoghq.com/
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    env,
6    ffi::{self, OsString},
7    path::{Path, PathBuf},
8};
9
10// reexport cc
11pub 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        // todo: improve handling of static and dynamic link cases
49
50        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    // cc::Build shadow
129    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}