halide_build/
lib.rs

1//! halide-build is used to compile [Halide](https://github.com/halide/halide) kernels
2
3use std::env;
4use std::fs::remove_file;
5use std::io;
6use std::path::PathBuf;
7use std::process::Command;
8
9static CARGO_LINK_SEARCH: &str = "cargo:rustc-link-search=native=";
10static CARGO_LINK_LIB: &str = "cargo:rustc-link-lib=";
11
12/// Link a library, specified by path and name
13pub fn link_lib(path: Option<&str>, name: &str) {
14    if let Some(path) = path {
15        println!("{}{}", CARGO_LINK_SEARCH, path);
16    }
17
18    println!("{}{}", CARGO_LINK_LIB, name);
19}
20
21/// Link a library, specified by filename
22pub fn link<P: AsRef<std::path::Path>>(filename: P) {
23    let mut filename = filename.as_ref().to_path_buf();
24    let name = filename.file_stem().expect("Invalid filename");
25    let s = String::from(name.to_str().expect("Invalid filename"));
26    let mut tmp: &str = &s;
27
28    if let Some(s) = s.strip_prefix("lib") {
29        tmp = &s[3..]
30    }
31
32    if s.ends_with(".a") {
33        tmp = &tmp[..tmp.len() - 2];
34    } else if s.ends_with(".so") {
35        tmp = &tmp[..tmp.len() - 3];
36    } else if s.ends_with(".dylib") {
37        tmp = &tmp[..tmp.len() - 6];
38    }
39
40    filename.pop();
41    link_lib(filename.to_str(), tmp);
42}
43
44/// Compile a shared library using the C++ compiler
45pub fn compile_shared_library(
46    compiler: Option<&str>,
47    output: &str,
48    args: &[&str],
49) -> Result<bool, std::io::Error> {
50    let cxx = std::env::var("CXX").unwrap_or_else(|_| "c++".to_owned());
51    let mut cmd = Command::new(compiler.unwrap_or(&cxx));
52
53    cmd.arg("-std=c++17");
54    let res = cmd
55        .arg("-shared")
56        .arg("-o")
57        .arg(output)
58        .args(args)
59        .status()?;
60    Ok(res.success())
61}
62
63/// Build stores the required context for building a Halide kernel
64#[derive(Debug)]
65pub struct Build<'a> {
66    /// Path to halide source
67    pub halide_path: PathBuf,
68
69    /// Input files
70    pub src: Vec<PathBuf>,
71
72    /// Output file
73    pub output: PathBuf,
74
75    /// C++ compiler
76    pub cxx: Option<&'a str>,
77
78    /// C++ compile time flags
79    pub cxxflags: Option<&'a str>,
80
81    /// C++ link time flags
82    pub ldflags: Option<&'a str>,
83
84    /// Extra arguments to build step
85    pub build_args: Vec<&'a str>,
86
87    /// Extra arguments to run step
88    pub run_args: Vec<&'a str>,
89
90    /// Keep executable when finished running
91    pub keep: bool,
92
93    /// Include Halide generator header
94    pub generator: bool,
95}
96
97impl<'a> Build<'a> {
98    /// Create a new build with the given halide path and output
99    pub fn new<P: AsRef<std::path::Path>, Q: AsRef<std::path::Path>>(
100        halide_path: P,
101        output: Q,
102    ) -> Build<'a> {
103        Build {
104            halide_path: halide_path.as_ref().to_path_buf(),
105            src: vec![],
106            output: output.as_ref().to_path_buf(),
107            cxx: None,
108            cxxflags: None,
109            ldflags: None,
110            build_args: vec![],
111            run_args: vec![],
112            keep: false,
113            generator: false,
114        }
115    }
116
117    pub fn source_file(mut self, src: impl AsRef<std::path::Path>) -> Self {
118        self.src.push(src.as_ref().to_owned());
119        self
120    }
121
122    pub fn build_arg(mut self, src: &'a str) -> Self {
123        self.build_args.push(src.as_ref());
124        self
125    }
126
127    pub fn build_args(mut self, src: impl AsRef<[&'a str]>) -> Self {
128        self.build_args.extend(src.as_ref());
129        self
130    }
131
132    pub fn run_arg(mut self, src: &'a str) -> Self {
133        self.run_args.push(src.as_ref());
134        self
135    }
136
137    pub fn run_args(mut self, src: impl AsRef<[&'a str]>) -> Self {
138        self.run_args.extend(src.as_ref());
139        self
140    }
141
142    pub fn ldflags(mut self, flags: &'a str) -> Self {
143        self.ldflags = Some(flags);
144        self
145    }
146
147    pub fn cxxflags(mut self, flags: &'a str) -> Self {
148        self.cxxflags = Some(flags);
149        self
150    }
151
152    pub fn compiler(mut self, name: &'a str) -> Self {
153        self.cxx = Some(name);
154        self
155    }
156
157    pub fn keep(mut self, x: bool) -> Self {
158        self.keep = x;
159        self
160    }
161
162    pub fn generator(mut self, x: bool) -> Self {
163        self.generator = x;
164        self
165    }
166
167    /// Execute the build step
168    pub fn build(&self) -> io::Result<bool> {
169        let cxx_default = env::var("CXX").unwrap_or_else(|_| "c++".to_string());
170        let mut cmd = Command::new(self.cxx.unwrap_or(cxx_default.as_str()));
171
172        cmd.arg("-std=c++17");
173        cmd.args(&["-I", &self.halide_path.join("include").to_string_lossy()])
174            .args(&["-I", &self.halide_path.join("tools").to_string_lossy()]);
175
176        if let Some(flags) = &self.cxxflags {
177            cmd.args(flags.split(' '));
178        }
179
180        if self.generator {
181            cmd.arg(
182                &self
183                    .halide_path
184                    .join("tools")
185                    .join("GenGen.cpp")
186                    .to_string_lossy()
187                    .as_ref(),
188            );
189        }
190
191        cmd.args(&self.build_args);
192
193        let tinfo = std::env::var("TERMINFO").unwrap_or_else(|_| "-lncurses".to_string());
194
195        cmd.args(&self.src)
196            .args(&["-o", &self.output.to_string_lossy()])
197            .args(&[
198                "-L",
199                &self.halide_path.join("lib").to_string_lossy(),
200                "-lHalide",
201                "-lpng",
202                "-ljpeg",
203                "-lpthread",
204                &tinfo,
205                "-ldl",
206                "-lz",
207            ]);
208
209        if let Some(flags) = &self.ldflags {
210            cmd.args(flags.split(' '));
211        }
212
213        cmd.status().map(|status| status.success())
214    }
215
216    /// Execute the run step
217    pub fn run(&self) -> io::Result<bool> {
218        if !self.output.exists() {
219            return Ok(false);
220        }
221
222        let res = Command::new(&self.output)
223            .args(&self.run_args)
224            .env("LD_LIBRARY_PATH", self.halide_path.join("lib"))
225            .status()
226            .map(|status| status.success());
227
228        if !self.keep {
229            let _ = remove_file(&self.output);
230        }
231
232        res
233    }
234}
235
236/// Source is used to maintain the Halide source directory
237pub struct Source {
238    pub halide_path: PathBuf,
239    pub repo: String,
240    pub branch: String,
241    pub make: String,
242    pub make_flags: Vec<String>,
243}
244
245impl Source {
246    /// Download Halide source for the first time
247    pub fn download(&self) -> io::Result<bool> {
248        Command::new("git")
249            .arg("clone")
250            .args(&["-b", self.branch.as_str()])
251            .arg(&self.repo)
252            .arg(&self.halide_path)
253            .status()
254            .map(|status| status.success())
255    }
256
257    /// Update Halide source
258    pub fn update(&self) -> io::Result<bool> {
259        Command::new("git")
260            .current_dir(&self.halide_path)
261            .arg("pull")
262            .arg("origin")
263            .arg(&self.branch)
264            .status()
265            .map(|status| status.success())
266    }
267
268    /// Build Halide source
269    pub fn build(&self) -> io::Result<bool> {
270        Command::new(&self.make)
271            .current_dir(&self.halide_path)
272            .args(&self.make_flags)
273            .status()
274            .map(|status| status.success())
275    }
276}