Skip to main content

slint_build/
lib.rs

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/*!
5This crate serves as a companion crate of the slint crate.
6It is meant to allow you to compile the `.slint` files from your `build.rs` script.
7
8The main entry point of this crate is the [`compile()`] function
9
10The generated code must be included in your crate by using the `slint::include_modules!()` macro.
11
12## Example
13
14In your Cargo.toml:
15
16```toml
17[package]
18...
19build = "build.rs"
20
21[dependencies]
22slint = "1.16.0"
23...
24
25[build-dependencies]
26slint-build = "1.16.0"
27```
28
29In the `build.rs` file:
30
31```ignore
32fn main() {
33    slint_build::compile("ui/hello.slint").unwrap();
34}
35```
36
37Then in your main file
38
39```ignore
40slint::include_modules!();
41fn main() {
42    HelloWorld::new().run();
43}
44```
45*/
46#![cfg_attr(
47    feature = "document-features",
48    doc = concat!("## Feature flags\n\n", document_features::document_features!())
49)]
50#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
51#![warn(missing_docs)]
52
53#[cfg(not(feature = "compat-1-18"))]
54compile_error!(
55    "The feature `compat-1-18` must be enabled to ensure \
56    forward compatibility with future version of this crate"
57);
58
59use std::collections::HashMap;
60use std::env;
61use std::io::{BufWriter, Write};
62use std::path::Path;
63
64use i_slint_compiler::diagnostics::BuildDiagnostics;
65
66/// Argument of [`CompilerConfiguration::with_default_translation_context()`]
67///
68pub use i_slint_compiler::DefaultTranslationContext;
69
70/// The structure for configuring aspects of the compilation of `.slint` markup files to Rust.
71#[derive(Clone)]
72pub struct CompilerConfiguration {
73    config: i_slint_compiler::CompilerConfiguration,
74}
75
76/// How should the Slint compiler embed images and fonts
77///
78/// Parameter of [`CompilerConfiguration::embed_resources()`]
79#[derive(Clone, PartialEq)]
80pub enum EmbedResourcesKind {
81    /// Resources are loaded from their absolute path at run-time.
82    ///
83    /// Only useful for debugging, since the files must still be present at the same path on the
84    /// machine running the application.
85    AsAbsolutePath,
86    /// The files referenced from .slint files are embedded in the binary as-is (for example
87    /// a PNG stays compressed), and decoded at run-time.
88    EmbedFiles,
89    #[cfg(feature = "renderer-software")]
90    /// Images and fonts are pre-processed at compile time and embedded as uncompressed pixel
91    /// data, ready to be drawn by the software renderer without any decoding at run-time.
92    ///
93    /// Useful for MCUs with no file system and little RAM.
94    /// Only the Slint software renderer can use these resources; Skia and FemtoVG can't.
95    EmbedForSoftwareRenderer,
96}
97
98impl Default for CompilerConfiguration {
99    fn default() -> Self {
100        Self {
101            config: i_slint_compiler::CompilerConfiguration::new(
102                i_slint_compiler::generator::OutputFormat::Rust,
103            ),
104        }
105    }
106}
107
108impl CompilerConfiguration {
109    /// Creates a new default configuration.
110    pub fn new() -> Self {
111        Self::default()
112    }
113
114    /// Create a new configuration that includes sets the include paths used for looking up
115    /// `.slint` imports to the specified vector of paths.
116    #[must_use]
117    pub fn with_include_paths(self, include_paths: Vec<std::path::PathBuf>) -> Self {
118        let mut config = self.config;
119        config.include_paths = include_paths;
120        Self { config }
121    }
122
123    /// Create a new configuration that sets the library paths used for looking up
124    /// `@library` imports to the specified map of paths.
125    ///
126    /// Each library path can either be a path to a `.slint` file or a directory.
127    /// If it's a file, the library is imported by its name prefixed by `@` (e.g.
128    /// `@example`). The specified file is the only entry-point for the library
129    /// and other files from the library won't be accessible from the outside.
130    /// If it's a directory, a specific file in that directory must be specified
131    /// when importing the library (e.g. `@example/widgets.slint`). This allows
132    /// exposing multiple entry-points for a single library.
133    ///
134    /// Compile `ui/main.slint` and specify an "example" library path:
135    /// ```rust,no_run
136    /// let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());
137    /// let library_paths = std::collections::HashMap::from([(
138    ///     "example".to_string(),
139    ///     manifest_dir.join("third_party/example/ui/lib.slint"),
140    /// )]);
141    /// let config = slint_build::CompilerConfiguration::new().with_library_paths(library_paths);
142    /// slint_build::compile_with_config("ui/main.slint", config).unwrap();
143    /// ```
144    ///
145    /// Import the "example" library in `ui/main.slint`:
146    /// ```slint,ignore
147    /// import { Example } from "@example";
148    /// ```
149    #[must_use]
150    pub fn with_library_paths(self, library_paths: HashMap<String, std::path::PathBuf>) -> Self {
151        let mut config = self.config;
152        config.library_paths = library_paths;
153        Self { config }
154    }
155
156    /// Create a new configuration that selects the style to be used for widgets.
157    #[must_use]
158    pub fn with_style(self, style: String) -> Self {
159        let mut config = self.config;
160        config.style = Some(style);
161        Self { config }
162    }
163
164    /// Selects how the resources such as images and font are processed.
165    ///
166    /// See [`EmbedResourcesKind`]
167    #[must_use]
168    pub fn embed_resources(self, kind: EmbedResourcesKind) -> Self {
169        let mut config = self.config;
170        config.embed_resources = match kind {
171            EmbedResourcesKind::AsAbsolutePath => {
172                i_slint_compiler::EmbedResourcesKind::OnlyBuiltinResources
173            }
174            EmbedResourcesKind::EmbedFiles => {
175                i_slint_compiler::EmbedResourcesKind::EmbedAllResources
176            }
177            #[cfg(feature = "renderer-software")]
178            EmbedResourcesKind::EmbedForSoftwareRenderer => {
179                i_slint_compiler::EmbedResourcesKind::EmbedTextures
180            }
181        };
182        Self { config }
183    }
184
185    /// Sets the scale factor to be applied to all `px` to `phx` conversions
186    /// as constant value. This is only intended for MCU environments. Use
187    /// in combination with [`Self::embed_resources`] to pre-scale images and glyphs
188    /// accordingly.
189    ///
190    /// If this is set, changing the scale factor at runtime will not have any effect.
191    #[must_use]
192    pub fn with_scale_factor(mut self, factor: f32) -> Self {
193        self.config.const_scale_factor = Some(factor);
194        self
195    }
196
197    /// Configures the compiler to bundle translations when compiling Slint code.
198    ///
199    /// It expects the path to be the root directory of the translation files.
200    ///
201    /// If given a relative path, it will be resolved relative to `$CARGO_MANIFEST_DIR`.
202    ///
203    /// The translation files should be in the gettext `.po` format and follow this pattern:
204    /// `<path>/<lang>/LC_MESSAGES/<crate>.po`
205    #[must_use]
206    pub fn with_bundled_translations(
207        self,
208        path: impl Into<std::path::PathBuf>,
209    ) -> CompilerConfiguration {
210        let mut config = self.config;
211        config.translation_path_bundle = Some(path.into());
212        Self { config }
213    }
214
215    /// Unless explicitly specified with the `@tr("context" => ...)`, the default translation context is the component name.
216    /// Use this option with [`DefaultTranslationContext::None`] to disable the default translation context.
217    ///
218    /// The translation file must also not have context
219    /// (`--no-default-translation-context` argument of `slint-tr-extractor`)
220    #[must_use]
221    pub fn with_default_translation_context(
222        mut self,
223        default_translation_context: DefaultTranslationContext,
224    ) -> Self {
225        self.config.default_translation_context = default_translation_context;
226        self
227    }
228
229    /// Configures the compiler to emit additional debug info when compiling Slint code.
230    ///
231    /// This is the equivalent to setting `SLINT_EMIT_DEBUG_INFO=1` and using the `slint!()` macro
232    /// and is primarily used by `i-slint-backend-testing`.
233    #[doc(hidden)]
234    #[must_use]
235    pub fn with_debug_info(self, enable: bool) -> Self {
236        let mut config = self.config;
237        config.debug_info = enable;
238        Self { config }
239    }
240
241    /// Configures the compiler to treat the Slint as part of a library.
242    ///
243    /// Use this when the components and types of the Slint code need
244    /// to be accessible from other modules.
245    ///
246    /// **Note**: This feature is experimental and may change or be removed in the future.
247    #[cfg(feature = "experimental-module-builds")]
248    #[must_use]
249    pub fn as_library(self, library_name: &str) -> Self {
250        let mut config = self.config;
251        config.library_name = Some(library_name.to_string());
252        Self { config }
253    }
254
255    /// Specify the Rust module to place the generated code in.
256    ///
257    /// **Note**: This feature is experimental and may change or be removed in the future.
258    #[cfg(feature = "experimental-module-builds")]
259    #[must_use]
260    pub fn rust_module(self, rust_module: &str) -> Self {
261        let mut config = self.config;
262        config.rust_module = Some(rust_module.to_string());
263        Self { config }
264    }
265    /// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts.
266    ///
267    /// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`],
268    /// and requires the `sdf-fonts` cargo feature to be enabled.
269    ///
270    /// [SDF](https://en.wikipedia.org/wiki/Signed_distance_function) reduces the binary size by
271    /// using an alternative representation for fonts, trading off some rendering quality
272    /// for a smaller binary footprint.
273    /// Rendering is slower and may result in slightly inferior visual output.
274    /// Use this on systems with limited flash memory.
275    #[cfg(feature = "sdf-fonts")]
276    #[must_use]
277    pub fn with_sdf_fonts(self, enable: bool) -> Self {
278        let mut config = self.config;
279        config.use_sdf_fonts = enable;
280        Self { config }
281    }
282
283    /// Converts any relative include_paths or library_paths to absolute paths relative to the manifest_dir.
284    #[must_use]
285    fn with_absolute_paths(self, manifest_dir: &std::path::Path) -> Self {
286        let mut config = self.config;
287
288        let to_absolute_path = |path: &mut std::path::PathBuf| {
289            if path.is_relative() {
290                *path = manifest_dir.join(&path);
291            }
292        };
293
294        for path in config.library_paths.values_mut() {
295            to_absolute_path(path);
296        }
297
298        for path in config.include_paths.iter_mut() {
299            to_absolute_path(path);
300        }
301
302        if let Some(path) = config.translation_path_bundle.as_mut() {
303            to_absolute_path(path);
304        }
305
306        Self { config }
307    }
308}
309
310/// Error returned by the `compile` function
311#[derive(derive_more::Error, derive_more::Display, Debug)]
312#[non_exhaustive]
313pub enum CompileError {
314    /// Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo.
315    #[display(
316        "Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo."
317    )]
318    NotRunViaCargo,
319    /// Parse error. The error are printed in the stderr, and also are in the vector
320    #[display("{_0:?}")]
321    CompileError(#[error(not(source))] Vec<String>),
322    /// Cannot write the generated file
323    #[display("Cannot write the generated file: {_0}")]
324    SaveError(std::io::Error),
325}
326
327struct CodeFormatter<Sink> {
328    indentation: usize,
329    /// We are currently in a string
330    in_string: bool,
331    /// number of bytes after the last `'`, 0 if there was none
332    in_char: usize,
333    /// In string or char, and the previous character was `\\`
334    escaped: bool,
335    sink: Sink,
336}
337
338impl<Sink> CodeFormatter<Sink> {
339    pub fn new(sink: Sink) -> Self {
340        Self { indentation: 0, in_string: false, in_char: 0, escaped: false, sink }
341    }
342}
343
344impl<Sink: Write> Write for CodeFormatter<Sink> {
345    fn write(&mut self, mut s: &[u8]) -> std::io::Result<usize> {
346        let len = s.len();
347        while let Some(idx) = s.iter().position(|c| match c {
348            b'{' if !self.in_string && self.in_char == 0 => {
349                self.indentation += 1;
350                true
351            }
352            b'}' if !self.in_string && self.in_char == 0 => {
353                self.indentation -= 1;
354                true
355            }
356            b';' if !self.in_string && self.in_char == 0 => true,
357            b'"' if !self.in_string && self.in_char == 0 => {
358                self.in_string = true;
359                self.escaped = false;
360                false
361            }
362            b'"' if self.in_string && !self.escaped => {
363                self.in_string = false;
364                false
365            }
366            b'\'' if !self.in_string && self.in_char == 0 => {
367                self.in_char = 1;
368                self.escaped = false;
369                false
370            }
371            b'\'' if !self.in_string && self.in_char > 0 && !self.escaped => {
372                self.in_char = 0;
373                false
374            }
375            b' ' | b'>' if self.in_char > 2 && !self.escaped => {
376                // probably a lifetime
377                self.in_char = 0;
378                false
379            }
380            b'\\' if (self.in_string || self.in_char > 0) && !self.escaped => {
381                self.escaped = true;
382                // no need to increment in_char since \ isn't a single character
383                false
384            }
385            _ if self.in_char > 0 => {
386                self.in_char += 1;
387                self.escaped = false;
388                false
389            }
390            _ => {
391                self.escaped = false;
392                false
393            }
394        }) {
395            let idx = idx + 1;
396            self.sink.write_all(&s[..idx])?;
397            self.sink.write_all(b"\n")?;
398            for _ in 0..self.indentation {
399                self.sink.write_all(b"    ")?;
400            }
401            s = &s[idx..];
402        }
403        self.sink.write_all(s)?;
404        Ok(len)
405    }
406    fn flush(&mut self) -> std::io::Result<()> {
407        self.sink.flush()
408    }
409}
410
411#[test]
412fn formatter_test() {
413    fn format_code(code: &str) -> String {
414        let mut res = Vec::new();
415        let mut formatter = CodeFormatter::new(&mut res);
416        formatter.write_all(code.as_bytes()).unwrap();
417        String::from_utf8(res).unwrap()
418    }
419
420    assert_eq!(
421        format_code("fn main() { if ';' == '}' { return \";\"; } else { panic!() } }"),
422        r#"fn main() {
423     if ';' == '}' {
424         return ";";
425         }
426     else {
427         panic!() }
428     }
429"#
430    );
431
432    assert_eq!(
433        format_code(r#"fn xx<'lt>(foo: &'lt str) { println!("{}", '\u{f700}'); return Ok(()); }"#),
434        r#"fn xx<'lt>(foo: &'lt str) {
435     println!("{}", '\u{f700}');
436     return Ok(());
437     }
438"#
439    );
440
441    assert_eq!(
442        format_code(r#"fn main() { ""; "'"; "\""; "{}"; "\\"; "\\\""; }"#),
443        r#"fn main() {
444     "";
445     "'";
446     "\"";
447     "{}";
448     "\\";
449     "\\\"";
450     }
451"#
452    );
453
454    assert_eq!(
455        format_code(r#"fn main() { '"'; '\''; '{'; '}'; '\\'; }"#),
456        r#"fn main() {
457     '"';
458     '\'';
459     '{';
460     '}';
461     '\\';
462     }
463"#
464    );
465}
466
467/// Compile the `.slint` file and generate rust code for it.
468///
469/// The generated code code will be created in the directory specified by
470/// the `OUT` environment variable as it is expected for build script.
471///
472/// The following line need to be added within your crate in order to include
473/// the generated code.
474/// ```ignore
475/// slint::include_modules!();
476/// ```
477///
478/// The path is relative to the `CARGO_MANIFEST_DIR`.
479///
480/// In case of compilation error, the errors are shown in `stderr`, the error
481/// are also returned in the [`CompileError`] enum. You must `unwrap` the returned
482/// result to make sure that cargo make the compilation fail in case there were
483/// errors when generating the code.
484///
485/// Please check out the documentation of the `slint` crate for more information
486/// about how to use the generated code.
487///
488/// This function can only be called within a build script run by cargo.
489///
490/// See also [`compile_with_config()`] if you want to specify a configuration.
491pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
492    compile_with_config(path, CompilerConfiguration::default())
493}
494
495/// Same as [`compile`], but allow to specify a configuration.
496///
497/// Compile `ui/hello.slint` and select the "material" style:
498/// ```rust,no_run
499/// let config =
500///     slint_build::CompilerConfiguration::new()
501///     .with_style("material".into());
502/// slint_build::compile_with_config("ui/hello.slint", config).unwrap();
503/// ```
504pub fn compile_with_config(
505    relative_slint_file_path: impl AsRef<std::path::Path>,
506    config: CompilerConfiguration,
507) -> Result<(), CompileError> {
508    let manifest_path = std::path::PathBuf::from(
509        env::var_os("CARGO_MANIFEST_DIR").ok_or(CompileError::NotRunViaCargo)?,
510    );
511    let config = config.with_absolute_paths(&manifest_path);
512
513    let path = manifest_path.join(relative_slint_file_path.as_ref());
514
515    let absolute_rust_output_file_path =
516        Path::new(&env::var_os("OUT_DIR").ok_or(CompileError::NotRunViaCargo)?).join(
517            path.file_stem()
518                .map(Path::new)
519                .unwrap_or_else(|| Path::new("slint_out"))
520                .with_extension("rs"),
521        );
522
523    #[cfg(feature = "experimental-module-builds")]
524    if let Some(library_name) = config.config.library_name.clone() {
525        println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name);
526        println!(
527            "cargo::metadata=SLINT_LIBRARY_PACKAGE={}",
528            std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default()
529        );
530        println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display());
531        if let Some(rust_module) = &config.config.rust_module {
532            println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module);
533        }
534    }
535    // Cargo scans a directory dependency recursively, so this also catches an added language.
536    if let Some(bundle_path) = &config.config.translation_path_bundle {
537        println!("cargo:rerun-if-changed={}", bundle_path.display());
538    }
539
540    let paths_dependencies =
541        compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
542
543    for path_dependency in paths_dependencies {
544        println!("cargo:rerun-if-changed={}", path_dependency.display());
545    }
546
547    println!("cargo:rerun-if-env-changed=SLINT_STYLE");
548    println!("cargo:rerun-if-env-changed=SLINT_FONT_SIZES");
549    println!("cargo:rerun-if-env-changed=SLINT_SCALE_FACTOR");
550    println!("cargo:rerun-if-env-changed=SLINT_ASSET_SECTION");
551    println!("cargo:rerun-if-env-changed=SLINT_EMBED_RESOURCES");
552    println!("cargo:rerun-if-env-changed=SLINT_EMIT_DEBUG_INFO");
553    println!("cargo:rerun-if-env-changed=SLINT_LIVE_PREVIEW");
554    println!("cargo:rerun-if-env-changed=SLINT_BUNDLE_TRANSLATIONS");
555
556    println!(
557        "cargo:rustc-env=SLINT_INCLUDE_GENERATED={}",
558        absolute_rust_output_file_path.display()
559    );
560
561    Ok(())
562}
563
564/// Similar to [`compile_with_config`], but meant to be used independently of cargo.
565///
566/// Will compile the input file and write the result in the given output file.
567///
568/// Both input_slint_file_path and output_rust_file_path should be absolute paths.
569///
570/// Doesn't print any cargo messages.
571///
572/// Returns a list of all input files that were used to generate the output file. (dependencies)
573pub fn compile_with_output_path(
574    input_slint_file_path: impl AsRef<std::path::Path>,
575    output_rust_file_path: impl AsRef<std::path::Path>,
576    config: CompilerConfiguration,
577) -> Result<Vec<std::path::PathBuf>, CompileError> {
578    let mut diag = BuildDiagnostics::default();
579    let syntax_node = i_slint_compiler::parser::parse_file(&input_slint_file_path, &mut diag);
580
581    if diag.has_errors() {
582        let vec = diag.to_string_vec();
583        diag.print();
584        return Err(CompileError::CompileError(vec));
585    }
586
587    let mut compiler_config = config.config;
588    compiler_config.translation_domain = std::env::var("CARGO_PKG_NAME").ok();
589
590    let syntax_node = syntax_node.expect("diags contained no compilation errors");
591
592    // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future
593    let (doc, diag, loader) =
594        spin_on::spin_on(i_slint_compiler::compile_syntax_node(syntax_node, diag, compiler_config));
595
596    if diag.has_errors()
597        || (!diag.is_empty() && std::env::var("SLINT_COMPILER_DENY_WARNINGS").is_ok())
598    {
599        let vec = diag.to_string_vec();
600        diag.print();
601        return Err(CompileError::CompileError(vec));
602    }
603
604    let output_file =
605        std::fs::File::create(&output_rust_file_path).map_err(CompileError::SaveError)?;
606    let mut code_formatter = CodeFormatter::new(BufWriter::new(output_file));
607    let generated = i_slint_compiler::generator::rust::generate(&doc, &loader.compiler_config)
608        .map_err(|e| CompileError::CompileError(vec![e.to_string()]))?;
609
610    let mut dependencies: Vec<std::path::PathBuf> = Vec::new();
611
612    for x in &diag.all_loaded_files {
613        if x.is_absolute() {
614            dependencies.push(x.clone());
615        }
616    }
617
618    // print warnings
619    diag.diagnostics_as_string().lines().for_each(|w| {
620        if !w.is_empty() {
621            println!("cargo:warning={}", w.strip_prefix("warning: ").unwrap_or(w))
622        }
623    });
624
625    write!(code_formatter, "{generated}").map_err(CompileError::SaveError)?;
626    dependencies.push(input_slint_file_path.as_ref().to_path_buf());
627
628    for er in doc.embedded_file_resources.borrow().iter() {
629        if let Some(resource) = er.path.as_deref()
630            && !resource.starts_with("builtin:")
631        {
632            dependencies.push(Path::new(resource).to_path_buf());
633        }
634    }
635
636    code_formatter.sink.flush().map_err(CompileError::SaveError)?;
637
638    Ok(dependencies)
639}
640
641/// This function is for use the application's build script, in order to print any device specific
642/// build flags reported by the backend
643pub fn print_rustc_flags() -> std::io::Result<()> {
644    if let Some(board_config_path) =
645        std::env::var_os("DEP_MCU_BOARD_SUPPORT_BOARD_CONFIG_PATH").map(std::path::PathBuf::from)
646    {
647        let config = std::fs::read_to_string(board_config_path.as_path())?;
648        let toml = config.parse::<toml_edit::DocumentMut>().expect("invalid board config toml");
649
650        for link_arg in
651            toml.get("link_args").and_then(toml_edit::Item::as_array).into_iter().flatten()
652        {
653            if let Some(option) = link_arg.as_str() {
654                println!("cargo:rustc-link-arg={option}");
655            }
656        }
657
658        for link_search_path in
659            toml.get("link_search_path").and_then(toml_edit::Item::as_array).into_iter().flatten()
660        {
661            if let Some(mut path) = link_search_path.as_str().map(std::path::PathBuf::from) {
662                if path.is_relative() {
663                    path = board_config_path.parent().unwrap().join(path);
664                }
665                println!("cargo:rustc-link-search={}", path.to_string_lossy());
666            }
667        }
668        println!("cargo:rerun-if-env-changed=DEP_MCU_BOARD_SUPPORT_MCU_BOARD_CONFIG_PATH");
669        println!("cargo:rerun-if-changed={}", board_config_path.display());
670    }
671
672    Ok(())
673}
674
675#[cfg(test)]
676fn root_path_prefix() -> std::path::PathBuf {
677    #[cfg(windows)]
678    return std::path::PathBuf::from("C:/");
679    #[cfg(not(windows))]
680    return std::path::PathBuf::from("/");
681}
682
683#[test]
684fn with_absolute_library_paths_test() {
685    use std::path::PathBuf;
686
687    let library_paths = std::collections::HashMap::from([
688        ("relative".to_string(), PathBuf::from("some/relative/path")),
689        ("absolute".to_string(), root_path_prefix().join("some/absolute/path")),
690    ]);
691    let config = CompilerConfiguration::new().with_library_paths(library_paths);
692
693    let manifest_path = root_path_prefix().join("path/to/manifest");
694    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
695    let relative = &absolute_config.config.library_paths["relative"];
696    assert!(relative.is_absolute());
697    assert!(relative.starts_with(&manifest_path));
698
699    assert!(!absolute_config.config.library_paths["absolute"].starts_with(&manifest_path));
700}
701
702#[test]
703fn with_absolute_include_paths_test() {
704    use std::path::PathBuf;
705
706    let config = CompilerConfiguration::new().with_include_paths(Vec::from([
707        root_path_prefix().join("some/absolute/path"),
708        PathBuf::from("some/relative/path"),
709    ]));
710
711    let manifest_path = root_path_prefix().join("path/to/manifest");
712    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
713    assert_eq!(
714        absolute_config.config.include_paths,
715        Vec::from([
716            root_path_prefix().join("some/absolute/path"),
717            manifest_path.join("some/relative/path"),
718        ])
719    )
720}