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 }