Skip to main content

libdd_common/
cc_utils.rs

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