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 .env("PKG_CONFIG", "pkg-config")
98 .arg(format!("OUT={}", out_dir.display()))
99 .arg("CC_STATIC_LIBRARY(libminigbm.pie.a)")
100 .current_dir(SOURCE_DIR)
101 .status()?;
102 if !status.success() {
103 bail!("make failed with status: {}", status);
104 }
105
106 // copy headers to build output
107 let src_dir = Path::new(SOURCE_DIR);
108 fs::copy(src_dir.join("gbm.h"), out_dir.join("gbm.h"))?;
109 fs::copy(
110 src_dir.join("minigbm_helpers.h"),
111 out_dir.join("minigbm_helpers.h"),
112 )?;
113
114 // minigbm will be linked using the name gbm, make sure it can be found.
115 fs::copy(out_dir.join("libminigbm.pie.a"), out_dir.join("libgbm.a"))?;
116
117 // write out a custom pkgconfig
118 let mut conf = File::create(pkgconfig_file)?;
119 let contents = format!(
120 r#"prefix={install_dir}
121 includedir=${{prefix}}
122 libdir=${{prefix}}
123
124 Name: libgbm
125 Description: A small gbm implementation
126 Version: 18.0.0
127 Cflags: -I${{includedir}}
128 Libs: -L${{libdir}} -lgbm
129 Requires.private: libdrm >= 2.4.50
130 "#,
131 install_dir = out_dir.display()
132 );
133 conf.write_all(contents.as_bytes())?;
134
135 // let pkg_config crate configure the cargo link metadata according to the custom pkgconfig
136 // above
137 env_prepend_pkg_config_path(out_dir)?;
138 let mut config = pkg_config::Config::new();
139 config.statik(true).probe("gbm")?;
140 Ok(())
141 }
142
minigbm() -> Result<()>143 fn minigbm() -> Result<()> {
144 if use_system_minigbm() {
145 pkg_config::probe_library("gbm").context("pkgconfig failed to find gbm")?;
146 } else {
147 // Otherwise build from source and emit cargo build metadata
148 let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("minigbm");
149 build_and_probe_minigbm(&out_dir).context("failed building minigbm")?;
150 };
151 Ok(())
152 }
153
154 /// Builds from pinned commit as static library and probes the generated pkgconfig file to emit
155 /// cargo linking metadata
build_and_probe_virglrenderer(out_dir: &Path) -> Result<()>156 fn build_and_probe_virglrenderer(out_dir: &Path) -> Result<()> {
157 const SOURCE_DIR: &str = "../third_party/virglrenderer";
158 let install_prefix = out_dir.join("installed");
159
160 println!("cargo:rerun-if-changed={}", SOURCE_DIR);
161
162 if !Path::new(SOURCE_DIR).join(".git").exists() {
163 bail!(
164 "{} source does not exist, did you forget to \
165 `git submodule update --init`?",
166 SOURCE_DIR
167 );
168 }
169
170 let mut platforms = vec!["egl"];
171 if env::var("CARGO_FEATURE_X").is_ok() {
172 platforms.push("glx");
173 }
174
175 // Ensures minigbm is available and that it's pkgconfig is locatable
176 minigbm()?;
177
178 let mut setup = Command::new("meson");
179 setup
180 .arg("setup")
181 .current_dir(SOURCE_DIR)
182 .arg("--prefix")
183 .arg(install_prefix.as_os_str())
184 .arg("--libdir")
185 .arg("lib")
186 .args(get_meson_cross_args())
187 .arg(format!("-Dplatforms={}", platforms.join(",")))
188 .arg("-Ddefault_library=static")
189 .arg(out_dir.as_os_str());
190
191 let setup_status = setup.status()?;
192 if !setup_status.success() {
193 bail!("meson setup failed with status: {}", setup_status);
194 }
195
196 let mut compile = Command::new("meson");
197 compile
198 .arg("compile")
199 .arg("src/virglrenderer")
200 .current_dir(out_dir);
201 let compile_status = compile.status()?;
202 if !compile_status.success() {
203 bail!("meson compile failed with status: {}", compile_status);
204 }
205
206 let mut install = Command::new("meson");
207 install.arg("install").current_dir(out_dir);
208 let install_status = install.status()?;
209 if !install_status.success() {
210 bail!("meson install failed with status: {}", install_status);
211 }
212
213 let pkg_config_path = install_prefix.join("lib/pkgconfig");
214 assert!(pkg_config_path.join("virglrenderer.pc").exists());
215
216 // let pkg_config crate configure the cargo link metadata according to the generated pkgconfig
217 env_prepend_pkg_config_path(pkg_config_path.as_path())?;
218 let mut config = pkg_config::Config::new();
219 config.statik(true).probe("virglrenderer")?;
220
221 Ok(())
222 }
223
virglrenderer() -> Result<()>224 fn virglrenderer() -> Result<()> {
225 if use_system_virglrenderer() && !use_system_minigbm() {
226 bail!("Must use system minigbm if using system virglrenderer (try setting CROSVM_USE_SYSTEM_MINIGBM=1)");
227 }
228
229 // Use virglrenderer package from pkgconfig on ChromeOS builds
230 if use_system_virglrenderer() {
231 let lib = pkg_config::Config::new()
232 .atleast_version("1.0.0")
233 .probe("virglrenderer")
234 .context("pkgconfig failed to find virglrenderer")?;
235 if lib.defines.contains_key("VIRGL_RENDERER_UNSTABLE_APIS") {
236 println!("cargo:rustc-cfg=virgl_renderer_unstable");
237 }
238 } else {
239 // Otherwise build from source.
240 let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("virglrenderer");
241 build_and_probe_virglrenderer(&out_dir)?;
242 }
243 Ok(())
244 }
245
gfxstream() -> Result<()>246 fn gfxstream() -> Result<()> {
247 if let Ok(gfxstream_path) = env::var("GFXSTREAM_PATH") {
248 println!("cargo:rustc-link-lib=gfxstream_backend");
249 println!("cargo:rustc-link-search={}", gfxstream_path);
250 Ok(())
251 } else {
252 let gfxstream_lib = pkg_config::Config::new().probe("gfxstream_backend")?;
253
254 if gfxstream_lib.defines.contains_key("GFXSTREAM_UNSTABLE") {
255 println!("cargo:rustc-cfg=gfxstream_unstable");
256 }
257
258 pkg_config::Config::new().probe("aemu_base")?;
259 pkg_config::Config::new().probe("aemu_host_common")?;
260 pkg_config::Config::new().probe("aemu_logging")?;
261 pkg_config::Config::new().probe("aemu_snapshot")?;
262
263 let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
264
265 if target_os.contains("linux") {
266 pkg_config::Config::new().probe("libdrm")?;
267 }
268
269 // Need to link against libc++ or libstdc++. Apple is clang-only, while by default other
270 // Unix platforms use libstdc++.
271 if target_os.contains("macos") {
272 println!("cargo:rustc-link-lib=dylib=c++");
273 } else if target_os.contains("linux") || target_os.contains("nto") {
274 println!("cargo:rustc-link-lib=dylib=stdc++");
275 }
276
277 Ok(())
278 }
279 }
280
main() -> Result<()>281 fn main() -> Result<()> {
282 println!("cargo:rustc-check-cfg=cfg(fence_passing_option1)");
283 println!("cargo:rustc-check-cfg=cfg(gfxstream_unstable)");
284 println!("cargo:rustc-check-cfg=cfg(virgl_renderer_unstable)");
285 let mut use_fence_passing_option1 = true;
286
287 // Skip installing dependencies when generating documents.
288 if env::var("CARGO_DOC").is_ok() {
289 return Ok(());
290 }
291
292 if env::var("CARGO_FEATURE_MINIGBM").is_ok() {
293 minigbm()?;
294 }
295
296 if env::var("CARGO_FEATURE_VIRGL_RENDERER").is_ok() {
297 virglrenderer()?;
298 use_fence_passing_option1 = false;
299 }
300
301 if env::var("CARGO_FEATURE_GFXSTREAM").is_ok()
302 && env::var("CARGO_FEATURE_GFXSTREAM_STUB").is_err()
303 {
304 gfxstream()?;
305 }
306
307 if use_fence_passing_option1 {
308 println!("cargo:rustc-cfg=fence_passing_option1");
309 }
310
311 Ok(())
312 }
313