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 }