Warning, /sdk/rust-qt-binding-generator/src/build.rs is written in an unsupported language. File is not indexed.
0001 extern crate cc; 0002 0003 use super::{generate_bindings, read_bindings_file, Config}; 0004 use regex::Regex; 0005 use serde_xml_rs::from_reader; 0006 use std::env::var; 0007 use std::io::{self, Write}; 0008 use std::path::{Path, PathBuf}; 0009 use std::process::Command; 0010 use std::time::{SystemTime, UNIX_EPOCH}; 0011 0012 #[derive(Deserialize)] 0013 struct Rcc { 0014 qresource: Vec<QResource>, 0015 } 0016 0017 #[derive(Deserialize)] 0018 struct QResource { 0019 #[allow(dead_code)] 0020 prefix: String, 0021 file: Vec<PathBuf>, 0022 } 0023 0024 /// Parse the qrc file, panic if it fails. 0025 fn read_qrc(qrc: &Path) -> Rcc { 0026 let bytes = std::fs::read(qrc) 0027 .unwrap_or_else(|e| panic!("Could not read as {} as UTF-8: {}", qrc.display(), e)); 0028 let mut rcc: Rcc = from_reader(&bytes[..]) 0029 .unwrap_or_else(|e| panic!("could not parse {}: {}", qrc.display(), e)); 0030 for qresource in &mut rcc.qresource { 0031 for file in &mut qresource.file { 0032 let mut p = qrc.parent().unwrap().to_path_buf(); 0033 p.push(&file); 0034 *file = p; 0035 } 0036 } 0037 rcc 0038 } 0039 0040 /// Get the list of files that are listed in the qrc file. 0041 fn qrc_to_input_list<'a>(qrc: &'a Path, rcc: &'a Rcc) -> Vec<&'a Path> { 0042 let mut list = Vec::new(); 0043 list.push(qrc); 0044 for qresource in &rcc.qresource { 0045 for file in &qresource.file { 0046 list.push(file); 0047 } 0048 } 0049 list 0050 } 0051 0052 /// Run a commmand and return the standard output if the command ran ok. 0053 /// Otherwise print an error and exit this program. 0054 fn run(cmd: &str, command: &mut Command) -> Vec<u8> { 0055 eprintln!("running: {:?}", command); 0056 match command.output() { 0057 Err(e) => eprintln!( 0058 "Could not run {}. Make sure {} is in your path: {}", 0059 cmd, cmd, e 0060 ), 0061 Ok(output) => { 0062 io::stderr() 0063 .write_all(&output.stderr) 0064 .expect("Could not write to stderr."); 0065 if output.status.success() { 0066 return output.stdout; 0067 } 0068 match output.status.code() { 0069 None => eprintln!("Process {} terminated by signal.", cmd), 0070 Some(code) => eprintln!("{} exited with status code: {}", cmd, code), 0071 } 0072 } 0073 } 0074 ::std::process::exit(-1); 0075 } 0076 0077 /// Query a Qt environment variable via qmake. 0078 fn qmake_query(var: &str) -> String { 0079 let v = String::from_utf8(run("qmake", Command::new("qmake").args(&["-query", var]))) 0080 .expect("qmake output was not valid UTF-8"); 0081 v.trim().to_string() 0082 } 0083 0084 fn qt_include_path() -> PathBuf { 0085 let qt_include_path = PathBuf::from(qmake_query("QT_INSTALL_HEADERS")); 0086 if qt_include_path.exists() { 0087 return qt_include_path; 0088 } 0089 // NixOS workaround: get value from CMAKE env variable 0090 let path = var("CMAKE_INCLUDE_PATH").expect("CMAKE_INCLUDE_PATH is not set"); 0091 let paths = path.split(':'); 0092 for path in paths { 0093 if path.contains("qtbase") { 0094 return path.into(); 0095 } 0096 } 0097 panic!("Could not find QT_INSTALL_HEADERS") 0098 } 0099 0100 struct Version { 0101 major: u8, 0102 minor: u8, 0103 patch: u8, 0104 } 0105 0106 /// Return the qt version number as an integer. 0107 /// 0108 /// # Panics 0109 /// 0110 /// Panic if the value could not be parsed. 0111 fn parse_qt_version(qt_version: &str) -> Version { 0112 let re = Regex::new(r"(\d)\.(\d{1,2})(\.(\d{1,2}))").unwrap(); 0113 match re.captures(qt_version) { 0114 None => panic!("Cannot parse Qt version number {}", qt_version), 0115 Some(cap) => Version { 0116 major: cap[1].parse::<u8>().unwrap(), 0117 minor: cap[2].parse::<u8>().unwrap(), 0118 patch: cap 0119 .get(4) 0120 .map(|m| m.as_str()) 0121 .unwrap_or("0") 0122 .parse::<u8>() 0123 .unwrap(), 0124 }, 0125 } 0126 } 0127 0128 /// Check for a minimal Qt version. 0129 /// 0130 /// # Example 0131 /// 0132 /// ``` 0133 /// # use std::env; 0134 /// # use rust_qt_binding_generator::build::require_qt_version; 0135 /// require_qt_version(5, 1, 0); 0136 /// ``` 0137 /// 0138 /// # Panics 0139 /// 0140 /// Panics if the installed version is smaller than the required version or if 0141 /// no version of Qt could be determined. 0142 pub fn require_qt_version(major: u8, minor: u8, patch: u8) { 0143 let qt_version = qmake_query("QT_VERSION"); 0144 let version = parse_qt_version(&qt_version); 0145 if version.major < major 0146 || (version.major == major 0147 && (version.minor < minor || (version.minor == minor && version.patch < patch))) 0148 { 0149 panic!( 0150 "Please use a version of Qt >= {}.{}.{}, not {}", 0151 major, minor, patch, qt_version 0152 ); 0153 } 0154 } 0155 0156 #[derive(Debug, PartialEq)] 0157 pub enum QtModule { 0158 Core, 0159 Gui, 0160 Multimedia, 0161 MultimediaWidgets, 0162 Network, 0163 Qml, 0164 Quick, 0165 QuickControls2, 0166 QuickTest, 0167 Sql, 0168 Test, 0169 Widgets, 0170 0171 Concurrent, 0172 DBus, 0173 Help, 0174 Location, 0175 OpenGL, 0176 Positioning, 0177 PrintSupport, 0178 Svg, 0179 WebChannel, 0180 WebEngine, 0181 WaylandCompositor, 0182 X11Extras, 0183 Xml, 0184 XmlPatterns, 0185 0186 Charts, 0187 0188 Kirigami2, 0189 } 0190 0191 impl QtModule { 0192 fn prefix(&self) -> &str { 0193 if *self == QtModule::Kirigami2 { 0194 "KF5" 0195 } else { 0196 "Qt5" 0197 } 0198 } 0199 } 0200 0201 /// A builder for binding generation and compilation of a Qt application. 0202 /// 0203 /// Pass options into this `Build` and then run `build` to generate bindings 0204 /// and compile the Qt C++ code and resources into a static library. 0205 /// This struct is meant to be used in a `build.rs` script. 0206 pub struct Build { 0207 qt_library_path: PathBuf, 0208 qt_include_path: PathBuf, 0209 out_dir: PathBuf, 0210 build: cc::Build, 0211 bindings: Vec<PathBuf>, 0212 qrc: Vec<PathBuf>, 0213 ui: Vec<PathBuf>, 0214 h: Vec<PathBuf>, 0215 cpp: Vec<PathBuf>, 0216 modules: Vec<QtModule>, 0217 link_libs: Vec<PathBuf>, 0218 } 0219 0220 impl Build { 0221 /// Create a new `Build` struct. 0222 /// 0223 /// Initialize the struct with the build output directory. That directory 0224 /// is available via the environment variable `OUT_DIR`. 0225 /// 0226 /// # Example 0227 /// 0228 /// ``` 0229 /// use std::env; 0230 /// use rust_qt_binding_generator::build::Build; 0231 /// if let Ok(out_dir) = env::var("OUT_DIR") { 0232 /// Build::new(&out_dir) 0233 /// .bindings("bindings.json") 0234 /// .qrc("qml.qrc") 0235 /// .ui("main.ui") 0236 /// .cpp("src/main.cpp") 0237 /// .compile("my_app"); 0238 /// } 0239 /// ``` 0240 pub fn new<P: AsRef<Path>>(out_dir: P) -> Build { 0241 let qt_include_path = qt_include_path(); 0242 let mut build = cc::Build::new(); 0243 build 0244 .cpp(true) 0245 .include(out_dir.as_ref()) 0246 .include(&qt_include_path); 0247 Build { 0248 qt_library_path: qmake_query("QT_INSTALL_LIBS").into(), 0249 qt_include_path, 0250 out_dir: out_dir.as_ref().to_path_buf(), 0251 build, 0252 bindings: Vec::new(), 0253 qrc: Vec::new(), 0254 ui: Vec::new(), 0255 h: Vec::new(), 0256 cpp: Vec::new(), 0257 modules: vec![QtModule::Core], 0258 link_libs: Vec::new(), 0259 } 0260 } 0261 /// Add a bindings file to be processed. 0262 pub fn bindings<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0263 let pb = path.as_ref().to_path_buf(); 0264 println!("cargo:rerun-if-changed={}", pb.display()); 0265 self.bindings.push(pb); 0266 self 0267 } 0268 /// Add a qrc file to be processed. 0269 /// 0270 /// qrc files are Qt Resource files. Files listed in a qrc file are 0271 /// compiled into a the binary. Here is an example qrc file that adds a qml 0272 /// file. 0273 /// ```xml 0274 /// <RCC> 0275 /// <qresource prefix="/"> 0276 /// <file>main.qml</file> 0277 /// </qresource> 0278 /// </RCC> 0279 /// ``` 0280 pub fn qrc<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0281 let pb = path.as_ref().to_path_buf(); 0282 println!("cargo:rerun-if-changed={}", pb.display()); 0283 self.qrc.push(pb); 0284 self 0285 } 0286 /// Add a ui file to be processed. 0287 /// 0288 /// ui files are generated by Qt Designer and describe how to build the user interface. 0289 pub fn ui<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0290 self.ui.push(path.as_ref().to_path_buf()); 0291 self 0292 } 0293 /// Add a C++ header file to be compiled into the program. 0294 pub fn h<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0295 let pb = path.as_ref().to_path_buf(); 0296 println!("cargo:rerun-if-changed={}", pb.display()); 0297 self.h.push(pb); 0298 self 0299 } 0300 /// Add a C++ file to be compiled into the program. 0301 pub fn cpp<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0302 self.cpp.push(path.as_ref().to_path_buf()); 0303 self 0304 } 0305 /// Add the path to the paths includes for headers. 0306 pub fn include_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0307 self.build.include(path); 0308 self 0309 } 0310 /// Link to the specified library. 0311 pub fn link_lib<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0312 self.link_libs.push(path.as_ref().to_path_buf()); 0313 self 0314 } 0315 fn print_link_libs(&self) { 0316 for path in &self.link_libs { 0317 if let Some(parent) = path.parent() { 0318 if parent.exists() { 0319 println!("cargo:rustc-link-search=native={}", parent.display()); 0320 } 0321 } 0322 if let Some(name) = path.file_name().and_then(|a| a.to_str()) { 0323 // remove 'lib' prefix and '.so...' suffix 0324 let name = if let Some(pos) = name.find('.') { 0325 &name[3..pos] 0326 } else { 0327 &name[3..] 0328 }; 0329 println!("cargo:rustc-link-lib={}", name); 0330 } 0331 } 0332 } 0333 /// Add a Qt module to be linked to the executable 0334 pub fn module(&mut self, module: QtModule) -> &mut Build { 0335 self.modules.push(module); 0336 self 0337 } 0338 /// Compile the static library. 0339 /// 0340 /// # Panics 0341 /// 0342 /// Panics if there is any kind of error. Run `cargo build -vv` to 0343 /// get more output while debugging. 0344 pub fn compile(&mut self, lib_name: &str) { 0345 for binding in &self.bindings { 0346 handle_binding(&self.out_dir, binding, &mut self.h, &mut self.cpp); 0347 } 0348 let mut compile_inputs: Vec<&Path> = Vec::new(); 0349 let ui_hs: Vec<_> = self.ui.iter().map(|ui| handle_ui(ui)).collect(); 0350 for ui_h in &ui_hs { 0351 compile_inputs.push(ui_h); 0352 } 0353 for h in &self.h { 0354 compile_inputs.push(h); 0355 handle_header(h, &mut self.cpp); 0356 } 0357 for qrc in &self.qrc { 0358 handle_qrc(&self.out_dir, qrc, &mut self.cpp); 0359 } 0360 for cpp in &self.cpp { 0361 compile_inputs.push(cpp); 0362 self.build.file(cpp); 0363 } 0364 // add the Qt module include folders 0365 for module in &self.modules { 0366 self.build 0367 .include(self.qt_include_path.join(&format!("Qt{:?}", module))); 0368 } 0369 let lib = self.out_dir.join(&format!("lib{}.a", lib_name)); 0370 if should_run(&compile_inputs, &[&lib]) { 0371 self.build.compile(lib_name); 0372 } else { 0373 // normally cc::Build outputs this information 0374 println!("cargo:rustc-link-lib=static={}", lib_name); 0375 println!("cargo:rustc-link-search=native={}", self.out_dir.display()); 0376 print_cpp_link_stdlib(); 0377 } 0378 println!("cargo:rustc-link-search={}", self.qt_library_path.display()); 0379 for module in &self.modules { 0380 println!("cargo:rustc-link-lib={}{:?}", module.prefix(), module); 0381 } 0382 self.print_link_libs(); 0383 } 0384 } 0385 0386 // Print the c++ library that cargo should link against 0387 // This is used when calling 'cargo build' when no actual recompile is needed. 0388 fn print_cpp_link_stdlib() { 0389 let target = ::std::env::var("TARGET").unwrap(); 0390 let stdlib = if target.contains("msvc") { 0391 None 0392 } else if target.contains("apple") || target.contains("freebsd") || target.contains("openbsd") { 0393 Some("c++") 0394 } else { 0395 Some("stdc++") 0396 }; 0397 if let Some(stdlib) = stdlib { 0398 println!("cargo:rustc-link-lib={}", stdlib); 0399 } 0400 } 0401 0402 /// Return true if all outputs and exist are older than the given input time. 0403 fn are_outputs_up_to_date(paths: &[&Path], input: SystemTime) -> bool { 0404 for path in paths { 0405 if let Ok(mt) = path.metadata().and_then(|m| m.modified()) { 0406 if mt <= input { 0407 let duration = input.duration_since(mt).unwrap(); 0408 eprintln!( 0409 "{} is outdated by {} seconds.", 0410 path.display(), 0411 duration.as_secs() 0412 ); 0413 return false; 0414 } 0415 } else { 0416 eprintln!("'{}' does not exist.", path.display()); 0417 return false; 0418 } 0419 } 0420 true 0421 } 0422 0423 /// Return the youngest/newest mtime for the paths. 0424 fn get_youngest_mtime(paths: &[&Path]) -> Result<SystemTime, String> { 0425 let mut max = UNIX_EPOCH; 0426 for path in paths { 0427 let mt = path 0428 .metadata() 0429 .and_then(|m| m.modified()) 0430 .map_err(|e| format!("Error reading file {}: {}.", path.display(), e))?; 0431 if mt > max { 0432 max = mt; 0433 } 0434 } 0435 Ok(max) 0436 } 0437 0438 /// Run moc to generate C++ code from a Qt C++ header 0439 fn moc(header: &Path, output: &Path) { 0440 run("moc", Command::new("moc").arg("-o").arg(output).arg(header)); 0441 } 0442 0443 /// Run rcc to generate C++ code from a Qt resource file 0444 fn rcc(rcfile: &Path, output: &Path) { 0445 run( 0446 "rcc", 0447 Command::new("rcc") 0448 .arg("--name") 0449 .arg(rcfile.file_stem().unwrap()) 0450 .arg("-o") 0451 .arg(output) 0452 .arg(rcfile), 0453 ); 0454 } 0455 0456 /// Run uic to generate C++ code from a QT ui file 0457 fn uic(uifile: &Path, output: &Path) { 0458 run("uic", Command::new("uic").arg("-o").arg(output).arg(uifile)); 0459 } 0460 0461 /// return true if a command should run. 0462 /// It returns true if all inputs are present and if any of the inputs is newer 0463 /// than the newest output or if the outputs do not exist yet. 0464 fn should_run(input: &[&Path], output: &[&Path]) -> bool { 0465 // get the youngest input time 0466 let input_time = match get_youngest_mtime(input) { 0467 Ok(time) => time, 0468 Err(e) => panic!("{}", e), 0469 }; 0470 !are_outputs_up_to_date(output, input_time) 0471 } 0472 0473 fn get_interface_module_path(config: &Config) -> PathBuf { 0474 let mut path = config.rust.dir.join("src"); 0475 path.push(&config.rust.interface_module); 0476 path.set_extension("rs"); 0477 PathBuf::new() 0478 } 0479 0480 fn handle_binding( 0481 out_dir: &Path, 0482 bindings_json: &Path, 0483 h: &mut Vec<PathBuf>, 0484 cpp: &mut Vec<PathBuf>, 0485 ) { 0486 let mut config = read_bindings_file(&bindings_json) 0487 .unwrap_or_else(|e| panic!("Could not parse {}: {}", bindings_json.display(), e)); 0488 let bindings_cpp = out_dir.join(&config.cpp_file); 0489 let mut bindings_h = bindings_cpp.clone(); 0490 bindings_h.set_extension("h"); 0491 config.cpp_file = bindings_cpp.clone(); 0492 config.rust.dir = out_dir.join(&config.rust.dir); 0493 let interface_rs = get_interface_module_path(&config); 0494 if should_run( 0495 &[bindings_json], 0496 &[&bindings_h, &bindings_cpp, &interface_rs], 0497 ) { 0498 generate_bindings(&config).unwrap(); 0499 } 0500 h.push(bindings_h); 0501 cpp.push(bindings_cpp); 0502 } 0503 0504 fn handle_qrc(out_dir: &Path, qrc_path: &Path, cpp: &mut Vec<PathBuf>) { 0505 let qrc = read_qrc(qrc_path); 0506 let qml_cpp = out_dir.join(format!( 0507 "qrc_{}.cpp", 0508 qrc_path.file_stem().unwrap().to_str().unwrap() 0509 )); 0510 let qrc_inputs = qrc_to_input_list(qrc_path, &qrc); 0511 if should_run(&qrc_inputs, &[&qml_cpp]) { 0512 rcc(qrc_path, &qml_cpp); 0513 } 0514 cpp.push(qml_cpp); 0515 } 0516 0517 fn handle_ui(ui: &Path) -> PathBuf { 0518 let ui_h = ui.parent().unwrap().join(format!( 0519 "ui_{}.h", 0520 ui.file_stem().unwrap().to_str().unwrap() 0521 )); 0522 0523 if should_run(&[ui], &[&ui_h]) { 0524 uic(ui, &ui_h); 0525 } 0526 ui_h 0527 } 0528 0529 fn handle_header(h: &Path, cpp: &mut Vec<PathBuf>) { 0530 let moc_file = h.parent().unwrap().join(format!( 0531 "moc_{}.cpp", 0532 h.file_stem().unwrap().to_str().unwrap() 0533 )); 0534 if should_run(&[h], &[&moc_file]) { 0535 moc(h, &moc_file); 0536 } 0537 cpp.push(moc_file); 0538 }