1 // Copyright 2021 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 use std::env;
6 use std::fs;
7 use std::fs::File;
8 use std::io::Write;
9 use std::path::Path;
10 use std::path::PathBuf;
11 use std::process::Command;
12
13 use anyhow::anyhow;
14 use anyhow::bail;
15 use anyhow::Context;
16 use anyhow::Result;
17
is_native_build() -> bool18 fn is_native_build() -> bool {
19 env::var("HOST").unwrap() == env::var("TARGET").unwrap()
20 }
21
use_system_minigbm() -> bool22 fn use_system_minigbm() -> bool {
23 println!("cargo:rerun-if-env-changed=CROSVM_BUILD_VARIANT");
24 println!("cargo:rerun-if-env-changed=CROSVM_USE_SYSTEM_MINIGBM");
25 env::var("CROSVM_BUILD_VARIANT").unwrap_or_default() == "chromeos"
26 || env::var("CROSVM_USE_SYSTEM_MINIGBM").unwrap_or_else(|_| "0".to_string()) != "0"
27 }
28
use_system_virglrenderer() -> bool29 fn use_system_virglrenderer() -> bool {
30 println!("cargo:rerun-if-env-changed=CROSVM_BUILD_VARIANT");
31 println!("cargo:rerun-if-env-changed=CROSVM_USE_SYSTEM_VIRGLRENDERER");
32 env::var("CROSVM_BUILD_VARIANT").unwrap_or_default() == "chromeos"
33 || env::var("CROSVM_USE_SYSTEM_VIRGLRENDERER").unwrap_or_else(|_| "0".to_string()) != "0"
34 }
35
36 /// Returns the target triplet prefix for gcc commands. No prefix is required
37 /// for native builds.
get_cross_compile_prefix() -> String38 fn get_cross_compile_prefix() -> String {
39 if is_native_build() {
40 return String::from("");
41 }
42
43 let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
44 let os = env::var("CARGO_CFG_TARGET_OS").unwrap();
45 let env = env::var("CARGO_CFG_TARGET_ENV").unwrap();
46 format!("{}-{}-{}-", arch, os, env)
47 }
48
49 /// For cross-compilation with meson, we need to pick a cross-file, which
50 /// live in /usr/local/share/meson/cross.
get_meson_cross_args() -> Vec<String>51 fn get_meson_cross_args() -> Vec<String> {
52 if is_native_build() {
53 Vec::new()
54 } else {
55 vec![
56 "--cross-file".to_string(),
57 env::var("CARGO_CFG_TARGET_ARCH").unwrap(),
58 ]
59 }
60 }
61
env_prepend_pkg_config_path(new_path: &Path) -> Result<()>62 fn env_prepend_pkg_config_path(new_path: &Path) -> Result<()> {
63 const KEY: &str = "PKG_CONFIG_PATH";
64 let new_path_string = new_path
65 .to_str()
66 .ok_or(anyhow!("failed to convert path to string"))?;
67 if let Ok(original_value) = env::var(KEY) {
68 env::set_var(KEY, format!("{}:{}", new_path_string, original_value));
69 } else {
70 env::set_var(KEY, new_path);
71 };
72 Ok(())
73 }
74
75 /// Builds from pinned commit as static library and probes the generated pkgconfig file to emit
76 /// cargo linking metadata
build_and_probe_minigbm(out_dir: &Path) -> Result<()>77 fn build_and_probe_minigbm(out_dir: &Path) -> Result<()> {
78 const SOURCE_DIR: &str = "../third_party/minigbm";
79 let pkgconfig_file = out_dir.join("gbm.pc");
80
81 println!("cargo:rerun-if-changed={}", SOURCE_DIR);
82
83 if !Path::new(SOURCE_DIR).join(".git").exists() {
84 bail!(
85 "{} source does not exist, did you forget to \
86 `git submodule update --init`?",
87 SOURCE_DIR
88 );
89 }
90
91 // build static library
92 let make_flags = env::var("CARGO_MAKEFLAGS").unwrap();
93 let status = Command::new("make")
94 .env("MAKEFLAGS", make_flags)
95 .env("VERBOSE", "1")
96 .env("CROSS_COMPILE", get_cross_compile_prefix())
97 .arg(format!("OUT={}", out_dir.display()))
98 .arg("CC_STATIC_LIBRARY(libminigbm.pie.a)")
99 .current_dir(SOURCE_DIR)
100 .status()?;
101 if !status.success() {
102 bail!("make failed with status: {}", status);
103 }
104
105 // copy headers to build output
106 let src_dir = Path::new(SOURCE_DIR);
107 fs::copy(src_dir.join("gbm.h"), out_dir.join("gbm.h"))?;
108 fs::copy(
109 src_dir.join("minigbm_helpers.h"),
110 out_dir.join("minigbm_helpers.h"),
111 )?;
112
113 // minigbm will be linked using the name gbm, make sure it can be found.
114 fs::copy(out_dir.join("libminigbm.pie.a"), out_dir.join("libgbm.a"))?;
115
116 // write out a custom pkgconfig
117 let mut conf = File::create(pkgconfig_file)?;
118 let contents = format!(
119 r#"prefix={install_dir}
120 includedir=${{prefix}}
121 libdir=${{prefix}}
122
123 Name: libgbm
124 Description: A small gbm implementation
125 Version: 18.0.0
126 Cflags: -I${{includedir}}
127 Libs: -L${{libdir}} -lgbm
128 Requires.private: libdrm >= 2.4.50
129 "#,
130 install_dir = out_dir.display()
131 );
132 conf.write_all(contents.as_bytes())?;
133
134 // let pkg_config crate configure the cargo link metadata according to the custom pkgconfig
135 // above
136 env_prepend_pkg_config_path(out_dir)?;
137 let mut config = pkg_config::Config::new();
138 config.statik(true).probe("gbm")?;
139 Ok(())
140 }
141
minigbm() -> Result<()>142 fn minigbm() -> Result<()> {
143 if use_system_minigbm() {
144 pkg_config::probe_library("gbm").context("pkgconfig failed to find gbm")?;
145 } else {
146 // Otherwise build from source and emit cargo build metadata
147 let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("minigbm");
148 build_and_probe_minigbm(&out_dir).context("failed building minigbm")?;
149 };
150 Ok(())
151 }
152
153 /// Builds from pinned commit as static library and probes the generated pkgconfig file to emit
154 /// cargo linking metadata
build_and_probe_virglrenderer(out_dir: &Path) -> Result<()>155 fn build_and_probe_virglrenderer(out_dir: &Path) -> Result<()> {
156 const SOURCE_DIR: &str = "../third_party/virglrenderer";
157 let install_prefix = out_dir.join("installed");
158
159 println!("cargo:rerun-if-changed={}", SOURCE_DIR);
160
161 if !Path::new(SOURCE_DIR).join(".git").exists() {
162 bail!(
163 "{} source does not exist, did you forget to \
164 `git submodule update --init`?",
165 SOURCE_DIR
166 );
167 }
168
169 let mut platforms = vec!["egl"];
170 if env::var("CARGO_FEATURE_X").is_ok() {
171 platforms.push("glx");
172 }
173
174 // Ensures minigbm is available and that it's pkgconfig is locatable
175 minigbm()?;
176
177 let mut setup = Command::new("meson");
178 setup
179 .arg("setup")
180 .current_dir(SOURCE_DIR)
181 .arg("--prefix")
182 .arg(install_prefix.as_os_str())
183 .arg("--libdir")
184 .arg("lib")
185 .args(get_meson_cross_args())
186 .arg(format!("-Dplatforms={}", platforms.join(",")))
187 .arg("-Ddefault_library=static")
188 .arg(out_dir.as_os_str());
189
190 let setup_status = setup.status()?;
191 if !setup_status.success() {
192 bail!("meson setup failed with status: {}", setup_status);
193 }
194
195 let mut compile = Command::new("meson");
196 compile
197 .arg("compile")
198 .arg("src/virglrenderer")
199 .current_dir(out_dir);
200 let compile_status = compile.status()?;
201 if !compile_status.success() {
202 bail!("meson compile failed with status: {}", compile_status);
203 }
204
205 let mut install = Command::new("meson");
206 install.arg("install").current_dir(out_dir);
207 let install_status = install.status()?;
208 if !install_status.success() {
209 bail!("meson install failed with status: {}", install_status);
210 }
211
212 let pkg_config_path = install_prefix.join("lib/pkgconfig");
213 assert!(pkg_config_path.join("virglrenderer.pc").exists());
214
215 // let pkg_config crate configure the cargo link metadata according to the generated pkgconfig
216 env_prepend_pkg_config_path(pkg_config_path.as_path())?;
217 let mut config = pkg_config::Config::new();
218 config.statik(true).probe("virglrenderer")?;
219
220 Ok(())
221 }
222
virglrenderer() -> Result<()>223 fn virglrenderer() -> Result<()> {
224 if use_system_virglrenderer() && !use_system_minigbm() {
225 bail!("Must use system minigbm if using system virglrenderer (try setting CROSVM_USE_SYSTEM_MINIGBM=1)");
226 }
227
228 // Use virglrenderer package from pkgconfig on ChromeOS builds
229 if use_system_virglrenderer() {
230 let lib = pkg_config::Config::new()
231 .atleast_version("1.0.0")
232 .probe("virglrenderer")
233 .context("pkgconfig failed to find virglrenderer")?;
234 if lib.defines.contains_key("VIRGL_RENDERER_UNSTABLE_APIS") {
235 println!("cargo:rustc-cfg=virgl_renderer_unstable");
236 }
237 } else {
238 // Otherwise build from source.
239 let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("virglrenderer");
240 build_and_probe_virglrenderer(&out_dir)?;
241 }
242 Ok(())
243 }
244
gfxstream() -> Result<()>245 fn gfxstream() -> Result<()> {
246 if let Ok(gfxstream_path) = env::var("GFXSTREAM_PATH") {
247 println!("cargo:rustc-link-lib=gfxstream_backend");
248 println!("cargo:rustc-link-search={}", gfxstream_path);
249 Ok(())
250 } else {
251 let gfxstream_lib = pkg_config::Config::new().probe("gfxstream_backend")?;
252
253 if gfxstream_lib.defines.contains_key("GFXSTREAM_UNSTABLE") {
254 println!("cargo:rustc-cfg=gfxstream_unstable");
255 }
256
257 pkg_config::Config::new().probe("aemu_base")?;
258 pkg_config::Config::new().probe("aemu_host_common")?;
259 pkg_config::Config::new().probe("aemu_logging")?;
260 pkg_config::Config::new().probe("aemu_snapshot")?;
261
262 let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
263
264 if target_os.contains("linux") {
265 pkg_config::Config::new().probe("libdrm")?;
266 }
267
268 // Need to link against libc++ or libstdc++. Apple is clang-only, while by default other
269 // Unix platforms use libstdc++.
270 if target_os.contains("macos") {
271 println!("cargo:rustc-link-lib=dylib=c++");
272 } else if target_os.contains("linux") || target_os.contains("nto") {
273 println!("cargo:rustc-link-lib=dylib=stdc++");
274 }
275
276 Ok(())
277 }
278 }
279
main() -> Result<()>280 fn main() -> Result<()> {
281 // Skip installing dependencies when generating documents.
282 if env::var("CARGO_DOC").is_ok() {
283 return Ok(());
284 }
285
286 if env::var("CARGO_FEATURE_MINIGBM").is_ok() {
287 minigbm()?;
288 }
289
290 if env::var("CARGO_FEATURE_VIRGL_RENDERER").is_ok() {
291 virglrenderer()?;
292 }
293
294 if env::var("CARGO_FEATURE_GFXSTREAM").is_ok()
295 && env::var("CARGO_FEATURE_GFXSTREAM_STUB").is_err()
296 {
297 gfxstream()?;
298 }
299
300 Ok(())
301 }
302