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 self.bindings.push(path.as_ref().to_path_buf()); 0264 self 0265 } 0266 /// Add a qrc file to be processed. 0267 /// 0268 /// qrc files are Qt Resource files. Files listed in a qrc file are 0269 /// compiled into a the binary. Here is an example qrc file that adds a qml 0270 /// file. 0271 /// ```xml 0272 /// <RCC> 0273 /// <qresource prefix="/"> 0274 /// <file>main.qml</file> 0275 /// </qresource> 0276 /// </RCC> 0277 /// ``` 0278 pub fn qrc<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0279 self.qrc.push(path.as_ref().to_path_buf()); 0280 self 0281 } 0282 /// Add a ui file to be processed. 0283 /// 0284 /// ui files are generated by Qt Designer and describe how to build the user interface. 0285 pub fn ui<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0286 self.ui.push(path.as_ref().to_path_buf()); 0287 self 0288 } 0289 /// Add a C++ header file to be compiled into the program. 0290 pub fn h<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0291 self.h.push(path.as_ref().to_path_buf()); 0292 self 0293 } 0294 /// Add a C++ file to be compiled into the program. 0295 pub fn cpp<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0296 self.cpp.push(path.as_ref().to_path_buf()); 0297 self 0298 } 0299 /// Add the path to the paths includes for headers. 0300 pub fn include_path<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0301 self.build.include(path); 0302 self 0303 } 0304 /// Link to the specified library. 0305 pub fn link_lib<P: AsRef<Path>>(&mut self, path: P) -> &mut Build { 0306 self.link_libs.push(path.as_ref().to_path_buf()); 0307 self 0308 } 0309 fn print_link_libs(&self) { 0310 for path in &self.link_libs { 0311 if let Some(parent) = path.parent() { 0312 if parent.exists() { 0313 println!("cargo:rustc-link-search=native={}", parent.display()); 0314 } 0315 } 0316 if let Some(name) = path.file_name().and_then(|a| a.to_str()) { 0317 // remove 'lib' prefix and '.so...' suffix 0318 let name = if let Some(pos) = name.find('.') { 0319 &name[3..pos] 0320 } else { 0321 &name[3..] 0322 }; 0323 println!("cargo:rustc-link-lib={}", name); 0324 } 0325 } 0326 } 0327 /// Add a Qt module to be linked to the executable 0328 pub fn module(&mut self, module: QtModule) -> &mut Build { 0329 self.modules.push(module); 0330 self 0331 } 0332 /// Compile the static library. 0333 /// 0334 /// # Panics 0335 /// 0336 /// Panics if there is any kind of error. Run `cargo build -vv` to 0337 /// get more output while debugging. 0338 pub fn compile(&mut self, lib_name: &str) { 0339 for binding in &self.bindings { 0340 handle_binding(&self.out_dir, binding, &mut self.h, &mut self.cpp); 0341 } 0342 let mut compile_inputs: Vec<&Path> = Vec::new(); 0343 let ui_hs: Vec<_> = self.ui.iter().map(|ui| handle_ui(ui)).collect(); 0344 for ui_h in &ui_hs { 0345 compile_inputs.push(ui_h); 0346 } 0347 for h in &self.h { 0348 compile_inputs.push(h); 0349 handle_header(h, &mut self.cpp); 0350 } 0351 for qrc in &self.qrc { 0352 handle_qrc(&self.out_dir, qrc, &mut self.cpp); 0353 } 0354 for cpp in &self.cpp { 0355 compile_inputs.push(cpp); 0356 self.build.file(cpp); 0357 } 0358 // add the Qt module include folders 0359 for module in &self.modules { 0360 self.build 0361 .include(self.qt_include_path.join(&format!("Qt{:?}", module))); 0362 } 0363 let lib = self.out_dir.join(&format!("lib{}.a", lib_name)); 0364 if should_run(&compile_inputs, &[&lib]) { 0365 self.build.compile(lib_name); 0366 } else { 0367 // normally cc::Build outputs this information 0368 println!("cargo:rustc-link-lib=static={}", lib_name); 0369 println!("cargo:rustc-link-search=native={}", self.out_dir.display()); 0370 print_cpp_link_stdlib(); 0371 } 0372 println!("cargo:rustc-link-search={}", self.qt_library_path.display()); 0373 for module in &self.modules { 0374 println!("cargo:rustc-link-lib={}{:?}", module.prefix(), module); 0375 } 0376 self.print_link_libs(); 0377 } 0378 } 0379 0380 // Print the c++ library that cargo should link against 0381 // This is used when calling 'cargo build' when no actual recompile is needed. 0382 fn print_cpp_link_stdlib() { 0383 let target = ::std::env::var("TARGET").unwrap(); 0384 let stdlib = if target.contains("msvc") { 0385 None 0386 } else if target.contains("apple") || target.contains("freebsd") || target.contains("openbsd") { 0387 Some("c++") 0388 } else { 0389 Some("stdc++") 0390 }; 0391 if let Some(stdlib) = stdlib { 0392 println!("cargo:rustc-link-lib={}", stdlib); 0393 } 0394 } 0395 0396 /// Return true if all outputs and exist are older than the given input time. 0397 fn are_outputs_up_to_date(paths: &[&Path], input: SystemTime) -> bool { 0398 for path in paths { 0399 if let Ok(mt) = path.metadata().and_then(|m| m.modified()) { 0400 if mt <= input { 0401 let duration = input.duration_since(mt).unwrap(); 0402 eprintln!( 0403 "{} is outdated by {} seconds.", 0404 path.display(), 0405 duration.as_secs() 0406 ); 0407 return false; 0408 } 0409 } else { 0410 eprintln!("'{}' does not exist.", path.display()); 0411 return false; 0412 } 0413 } 0414 true 0415 } 0416 0417 /// Return the youngest/newest mtime for the paths. 0418 fn get_youngest_mtime(paths: &[&Path]) -> Result<SystemTime, String> { 0419 let mut max = UNIX_EPOCH; 0420 for path in paths { 0421 let mt = path 0422 .metadata() 0423 .and_then(|m| m.modified()) 0424 .map_err(|e| format!("Error reading file {}: {}.", path.display(), e))?; 0425 if mt > max { 0426 max = mt; 0427 } 0428 } 0429 Ok(max) 0430 } 0431 0432 /// Run moc to generate C++ code from a Qt C++ header 0433 fn moc(header: &Path, output: &Path) { 0434 run("moc", Command::new("moc").arg("-o").arg(output).arg(header)); 0435 } 0436 0437 /// Run rcc to generate C++ code from a Qt resource file 0438 fn rcc(rcfile: &Path, output: &Path) { 0439 run( 0440 "rcc", 0441 Command::new("rcc") 0442 .arg("--name") 0443 .arg(rcfile.file_stem().unwrap()) 0444 .arg("-o") 0445 .arg(output) 0446 .arg(rcfile), 0447 ); 0448 } 0449 0450 /// Run uic to generate C++ code from a QT ui file 0451 fn uic(uifile: &Path, output: &Path) { 0452 run("uic", Command::new("uic").arg("-o").arg(output).arg(uifile)); 0453 } 0454 0455 /// return true if a command should run. 0456 /// It returns true if all inputs are present and if any of the inputs is newer 0457 /// than the newest output or if the outputs do not exist yet. 0458 fn should_run(input: &[&Path], output: &[&Path]) -> bool { 0459 // get the youngest input time 0460 let input_time = match get_youngest_mtime(input) { 0461 Ok(time) => time, 0462 Err(e) => panic!("{}", e), 0463 }; 0464 !are_outputs_up_to_date(output, input_time) 0465 } 0466 0467 fn get_interface_module_path(config: &Config) -> PathBuf { 0468 let mut path = config.rust.dir.join("src"); 0469 path.push(&config.rust.interface_module); 0470 path.set_extension("rs"); 0471 PathBuf::new() 0472 } 0473 0474 fn handle_binding( 0475 out_dir: &Path, 0476 bindings_json: &Path, 0477 h: &mut Vec<PathBuf>, 0478 cpp: &mut Vec<PathBuf>, 0479 ) { 0480 let mut config = read_bindings_file(&bindings_json) 0481 .unwrap_or_else(|e| panic!("Could not parse {}: {}", bindings_json.display(), e)); 0482 let bindings_cpp = out_dir.join(&config.cpp_file); 0483 let mut bindings_h = bindings_cpp.clone(); 0484 bindings_h.set_extension("h"); 0485 config.cpp_file = bindings_cpp.clone(); 0486 config.rust.dir = out_dir.join(&config.rust.dir); 0487 let interface_rs = get_interface_module_path(&config); 0488 if should_run( 0489 &[bindings_json], 0490 &[&bindings_h, &bindings_cpp, &interface_rs], 0491 ) { 0492 generate_bindings(&config).unwrap(); 0493 } 0494 h.push(bindings_h); 0495 cpp.push(bindings_cpp); 0496 } 0497 0498 fn handle_qrc(out_dir: &Path, qrc_path: &Path, cpp: &mut Vec<PathBuf>) { 0499 let qrc = read_qrc(qrc_path); 0500 let qml_cpp = out_dir.join(format!( 0501 "qrc_{}.cpp", 0502 qrc_path.file_stem().unwrap().to_str().unwrap() 0503 )); 0504 let qrc_inputs = qrc_to_input_list(qrc_path, &qrc); 0505 if should_run(&qrc_inputs, &[&qml_cpp]) { 0506 rcc(qrc_path, &qml_cpp); 0507 } 0508 cpp.push(qml_cpp); 0509 } 0510 0511 fn handle_ui(ui: &Path) -> PathBuf { 0512 let ui_h = ui.parent().unwrap().join(format!( 0513 "ui_{}.h", 0514 ui.file_stem().unwrap().to_str().unwrap() 0515 )); 0516 0517 if should_run(&[ui], &[&ui_h]) { 0518 uic(ui, &ui_h); 0519 } 0520 ui_h 0521 } 0522 0523 fn handle_header(h: &Path, cpp: &mut Vec<PathBuf>) { 0524 let moc_file = h.parent().unwrap().join(format!( 0525 "moc_{}.cpp", 0526 h.file_stem().unwrap().to_str().unwrap() 0527 )); 0528 if should_run(&[h], &[&moc_file]) { 0529 moc(h, &moc_file); 0530 } 0531 cpp.push(moc_file); 0532 }