xref: /aosp_15_r20/external/crosvm/rutabaga_gfx/build.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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