| 1 | // Copyright © SixtyFPS GmbH <info@slint.dev> |
| 2 | // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 |
| 3 | |
| 4 | use std::io::{BufWriter, Write}; |
| 5 | use std::path::Path; |
| 6 | |
| 7 | fn main() -> std::io::Result<()> { |
| 8 | let mut generated_file = BufWriter::new(std::fs::File::create( |
| 9 | Path::new(&std::env::var_os("OUT_DIR" ).unwrap()).join("generated.rs" ), |
| 10 | )?); |
| 11 | |
| 12 | for testcase in test_driver_lib::collect_test_cases("cases" )? { |
| 13 | println!("cargo:rerun-if-changed= {}" , testcase.absolute_path.display()); |
| 14 | let mut module_name = testcase.identifier(); |
| 15 | if module_name.starts_with(|c: char| !c.is_ascii_alphabetic()) { |
| 16 | module_name.insert(0, '_' ); |
| 17 | } |
| 18 | writeln!(generated_file, "#[path= \"{module_name}.rs \"] mod r# {module_name};" )?; |
| 19 | let source = std::fs::read_to_string(&testcase.absolute_path)?; |
| 20 | let ignored = if testcase.is_ignored("rust" ) { |
| 21 | "#[ignore = \"testcase ignored for rust \"]" |
| 22 | } else if cfg!(not(feature = "build-time" )) && source.contains("//bundle-translations" ) { |
| 23 | "#[ignore = \"translation bundle not working with the macro \"]" |
| 24 | } else { |
| 25 | "" |
| 26 | }; |
| 27 | |
| 28 | let mut output = BufWriter::new(std::fs::File::create( |
| 29 | Path::new(&std::env::var_os("OUT_DIR" ).unwrap()).join(format!(" {module_name}.rs" )), |
| 30 | )?); |
| 31 | |
| 32 | #[cfg (not(feature = "build-time" ))] |
| 33 | if !generate_macro(&source, &mut output, testcase)? { |
| 34 | continue; |
| 35 | } |
| 36 | #[cfg (feature = "build-time" )] |
| 37 | generate_source(&source, &mut output, testcase)?; |
| 38 | |
| 39 | for (i, x) in test_driver_lib::extract_test_functions(&source) |
| 40 | .filter(|x| x.language_id == "rust" ) |
| 41 | .enumerate() |
| 42 | { |
| 43 | write!( |
| 44 | output, |
| 45 | r" |
| 46 | #[test] {} fn t_ {}() -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {{ |
| 47 | use i_slint_backend_testing as slint_testing; |
| 48 | slint_testing::init_no_event_loop(); |
| 49 | {} |
| 50 | Ok(()) |
| 51 | }}" , |
| 52 | ignored, |
| 53 | i, |
| 54 | x.source.replace(' \n' , " \n " ) |
| 55 | )?; |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | // By default resources are embedded. The WASM example builds provide test coverage for that. This switch |
| 60 | // provides test coverage for the non-embedding case, compiling tests without embedding the images. |
| 61 | println!("cargo:rustc-env=SLINT_EMBED_RESOURCES=false" ); |
| 62 | |
| 63 | //Make sure to use a consistent style |
| 64 | println!("cargo:rustc-env=SLINT_STYLE=fluent" ); |
| 65 | println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1" ); |
| 66 | println!("cargo:rustc-env=SLINT_EMIT_DEBUG_INFO=1" ); |
| 67 | Ok(()) |
| 68 | } |
| 69 | |
| 70 | #[cfg (not(feature = "build-time" ))] |
| 71 | fn generate_macro( |
| 72 | source: &str, |
| 73 | output: &mut dyn Write, |
| 74 | testcase: test_driver_lib::TestCase, |
| 75 | ) -> Result<bool, std::io::Error> { |
| 76 | if source.contains(" \\{" ) { |
| 77 | // Unfortunately, \{ is not valid in a rust string so it cannot be used in a slint! macro |
| 78 | output.write_all(b"#[test] #[ignore = \"string template don't work in macros \"] fn ignored_because_string_template() {{}}" )?; |
| 79 | return Ok(false); |
| 80 | } |
| 81 | // to silence all the warnings in .slint files that would be turned into errors |
| 82 | output.write_all(b"#![allow(deprecated)]" )?; |
| 83 | let include_paths = test_driver_lib::extract_include_paths(source); |
| 84 | let library_paths = test_driver_lib::extract_library_paths(source); |
| 85 | output.write_all(b"slint::slint!{" )?; |
| 86 | for path in include_paths { |
| 87 | let mut abs_path = testcase.absolute_path.clone(); |
| 88 | abs_path.pop(); |
| 89 | abs_path.push(path); |
| 90 | |
| 91 | output.write_all(b"#[include_path=r# \"" )?; |
| 92 | output.write_all(abs_path.to_string_lossy().as_bytes())?; |
| 93 | output.write_all(b" \"#] \n" )?; |
| 94 | |
| 95 | println!("cargo:rerun-if-changed= {}" , abs_path.to_string_lossy()); |
| 96 | } |
| 97 | for (lib, path) in library_paths { |
| 98 | let mut abs_path = testcase.absolute_path.clone(); |
| 99 | abs_path.pop(); |
| 100 | abs_path.push(path); |
| 101 | |
| 102 | output.write_all(b"#[library_path(" )?; |
| 103 | output.write_all(lib.as_bytes())?; |
| 104 | output.write_all(b")=r# \"" )?; |
| 105 | output.write_all(abs_path.to_string_lossy().as_bytes())?; |
| 106 | output.write_all(b" \"#] \n" )?; |
| 107 | |
| 108 | println!("cargo:rerun-if-changed= {}" , abs_path.to_string_lossy()); |
| 109 | } |
| 110 | |
| 111 | if let Some(style) = testcase.requested_style { |
| 112 | output.write_all(b"#[style= \"" )?; |
| 113 | output.write_all(style.as_bytes())?; |
| 114 | output.write_all(b" \"#] \n" )?; |
| 115 | } |
| 116 | |
| 117 | let mut abs_path = testcase.absolute_path; |
| 118 | abs_path.pop(); |
| 119 | output.write_all(b"#[include_path=r# \"" )?; |
| 120 | output.write_all(abs_path.to_string_lossy().as_bytes())?; |
| 121 | output.write_all(b" \"#] \n" )?; |
| 122 | output.write_all(source.as_bytes())?; |
| 123 | output.write_all(b"} \n" )?; |
| 124 | Ok(true) |
| 125 | } |
| 126 | |
| 127 | #[cfg (feature = "build-time" )] |
| 128 | fn generate_source( |
| 129 | source: &str, |
| 130 | output: &mut impl Write, |
| 131 | testcase: test_driver_lib::TestCase, |
| 132 | ) -> Result<(), std::io::Error> { |
| 133 | use i_slint_compiler::{diagnostics::BuildDiagnostics, *}; |
| 134 | |
| 135 | let include_paths = test_driver_lib::extract_include_paths(source) |
| 136 | .map(std::path::PathBuf::from) |
| 137 | .collect::<Vec<_>>(); |
| 138 | let library_paths = test_driver_lib::extract_library_paths(source) |
| 139 | .map(|(k, v)| (k.to_string(), std::path::PathBuf::from(v))) |
| 140 | .collect::<std::collections::HashMap<_, _>>(); |
| 141 | |
| 142 | let mut diag = BuildDiagnostics::default(); |
| 143 | let syntax_node = parser::parse(source.to_owned(), Some(&testcase.absolute_path), &mut diag); |
| 144 | let mut compiler_config = CompilerConfiguration::new(generator::OutputFormat::Rust); |
| 145 | compiler_config.enable_experimental = true; |
| 146 | compiler_config.include_paths = include_paths; |
| 147 | compiler_config.library_paths = library_paths; |
| 148 | compiler_config.style = Some(testcase.requested_style.unwrap_or("fluent" ).to_string()); |
| 149 | compiler_config.debug_info = true; |
| 150 | if source.contains("//bundle-translations" ) { |
| 151 | compiler_config.translation_path_bundle = |
| 152 | Some(testcase.absolute_path.parent().unwrap().to_path_buf()); |
| 153 | compiler_config.translation_domain = |
| 154 | Some(testcase.absolute_path.file_stem().unwrap().to_str().unwrap().to_string()); |
| 155 | } |
| 156 | let (root_component, diag, loader) = |
| 157 | spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config)); |
| 158 | |
| 159 | if diag.has_errors() { |
| 160 | diag.print_warnings_and_exit_on_error(); |
| 161 | return Err(std::io::Error::new( |
| 162 | std::io::ErrorKind::Other, |
| 163 | format!("build error in {:?}" , testcase.absolute_path), |
| 164 | )); |
| 165 | } else { |
| 166 | diag.print(); |
| 167 | } |
| 168 | |
| 169 | generator::generate( |
| 170 | generator::OutputFormat::Rust, |
| 171 | output, |
| 172 | &root_component, |
| 173 | &loader.compiler_config, |
| 174 | )?; |
| 175 | Ok(()) |
| 176 | } |
| 177 | |