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        Self { config }
303    }
304}
305
306/// Error returned by the `compile` function
307#[derive(derive_more::Error, derive_more::Display, Debug)]
308#[non_exhaustive]
309pub enum CompileError {
310    /// Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo.
311    #[display(
312        "Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo."
313    )]
314    NotRunViaCargo,
315    /// Parse error. The error are printed in the stderr, and also are in the vector
316    #[display("{_0:?}")]
317    CompileError(#[error(not(source))] Vec<String>),
318    /// Cannot write the generated file
319    #[display("Cannot write the generated file: {_0}")]
320    SaveError(std::io::Error),
321}
322
323struct CodeFormatter<Sink> {
324    indentation: usize,
325    /// We are currently in a string
326    in_string: bool,
327    /// number of bytes after the last `'`, 0 if there was none
328    in_char: usize,
329    /// In string or char, and the previous character was `\\`
330    escaped: bool,
331    sink: Sink,
332}
333
334impl<Sink> CodeFormatter<Sink> {
335    pub fn new(sink: Sink) -> Self {
336        Self { indentation: 0, in_string: false, in_char: 0, escaped: false, sink }
337    }
338}
339
340impl<Sink: Write> Write for CodeFormatter<Sink> {
341    fn write(&mut self, mut s: &[u8]) -> std::io::Result<usize> {
342        let len = s.len();
343        while let Some(idx) = s.iter().position(|c| match c {
344            b'{' if !self.in_string && self.in_char == 0 => {
345                self.indentation += 1;
346                true
347            }
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 => true,
353            b'"' if !self.in_string && self.in_char == 0 => {
354                self.in_string = true;
355                self.escaped = false;
356                false
357            }
358            b'"' if self.in_string && !self.escaped => {
359                self.in_string = false;
360                false
361            }
362            b'\'' if !self.in_string && self.in_char == 0 => {
363                self.in_char = 1;
364                self.escaped = false;
365                false
366            }
367            b'\'' if !self.in_string && self.in_char > 0 && !self.escaped => {
368                self.in_char = 0;
369                false
370            }
371            b' ' | b'>' if self.in_char > 2 && !self.escaped => {
372                // probably a lifetime
373                self.in_char = 0;
374                false
375            }
376            b'\\' if (self.in_string || self.in_char > 0) && !self.escaped => {
377                self.escaped = true;
378                // no need to increment in_char since \ isn't a single character
379                false
380            }
381            _ if self.in_char > 0 => {
382                self.in_char += 1;
383                self.escaped = false;
384                false
385            }
386            _ => {
387                self.escaped = false;
388                false
389            }
390        }) {
391            let idx = idx + 1;
392            self.sink.write_all(&s[..idx])?;
393            self.sink.write_all(b"\n")?;
394            for _ in 0..self.indentation {
395                self.sink.write_all(b"    ")?;
396            }
397            s = &s[idx..];
398        }
399        self.sink.write_all(s)?;
400        Ok(len)
401    }
402    fn flush(&mut self) -> std::io::Result<()> {
403        self.sink.flush()
404    }
405}
406
407#[test]
408fn formatter_test() {
409    fn format_code(code: &str) -> String {
410        let mut res = Vec::new();
411        let mut formatter = CodeFormatter::new(&mut res);
412        formatter.write_all(code.as_bytes()).unwrap();
413        String::from_utf8(res).unwrap()
414    }
415
416    assert_eq!(
417        format_code("fn main() { if ';' == '}' { return \";\"; } else { panic!() } }"),
418        r#"fn main() {
419     if ';' == '}' {
420         return ";";
421         }
422     else {
423         panic!() }
424     }
425"#
426    );
427
428    assert_eq!(
429        format_code(r#"fn xx<'lt>(foo: &'lt str) { println!("{}", '\u{f700}'); return Ok(()); }"#),
430        r#"fn xx<'lt>(foo: &'lt str) {
431     println!("{}", '\u{f700}');
432     return Ok(());
433     }
434"#
435    );
436
437    assert_eq!(
438        format_code(r#"fn main() { ""; "'"; "\""; "{}"; "\\"; "\\\""; }"#),
439        r#"fn main() {
440     "";
441     "'";
442     "\"";
443     "{}";
444     "\\";
445     "\\\"";
446     }
447"#
448    );
449
450    assert_eq!(
451        format_code(r#"fn main() { '"'; '\''; '{'; '}'; '\\'; }"#),
452        r#"fn main() {
453     '"';
454     '\'';
455     '{';
456     '}';
457     '\\';
458     }
459"#
460    );
461}
462
463/// Compile the `.slint` file and generate rust code for it.
464///
465/// The generated code code will be created in the directory specified by
466/// the `OUT` environment variable as it is expected for build script.
467///
468/// The following line need to be added within your crate in order to include
469/// the generated code.
470/// ```ignore
471/// slint::include_modules!();
472/// ```
473///
474/// The path is relative to the `CARGO_MANIFEST_DIR`.
475///
476/// In case of compilation error, the errors are shown in `stderr`, the error
477/// are also returned in the [`CompileError`] enum. You must `unwrap` the returned
478/// result to make sure that cargo make the compilation fail in case there were
479/// errors when generating the code.
480///
481/// Please check out the documentation of the `slint` crate for more information
482/// about how to use the generated code.
483///
484/// This function can only be called within a build script run by cargo.
485///
486/// See also [`compile_with_config()`] if you want to specify a configuration.
487pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
488    compile_with_config(path, CompilerConfiguration::default())
489}
490
491/// Same as [`compile`], but allow to specify a configuration.
492///
493/// Compile `ui/hello.slint` and select the "material" style:
494/// ```rust,no_run
495/// let config =
496///     slint_build::CompilerConfiguration::new()
497///     .with_style("material".into());
498/// slint_build::compile_with_config("ui/hello.slint", config).unwrap();
499/// ```
500pub fn compile_with_config(
501    relative_slint_file_path: impl AsRef<std::path::Path>,
502    config: CompilerConfiguration,
503) -> Result<(), CompileError> {
504    let manifest_path = std::path::PathBuf::from(
505        env::var_os("CARGO_MANIFEST_DIR").ok_or(CompileError::NotRunViaCargo)?,
506    );
507    let config = config.with_absolute_paths(&manifest_path);
508
509    let path = manifest_path.join(relative_slint_file_path.as_ref());
510
511    let absolute_rust_output_file_path =
512        Path::new(&env::var_os("OUT_DIR").ok_or(CompileError::NotRunViaCargo)?).join(
513            path.file_stem()
514                .map(Path::new)
515                .unwrap_or_else(|| Path::new("slint_out"))
516                .with_extension("rs"),
517        );
518
519    #[cfg(feature = "experimental-module-builds")]
520    if let Some(library_name) = config.config.library_name.clone() {
521        println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name);
522        println!(
523            "cargo::metadata=SLINT_LIBRARY_PACKAGE={}",
524            std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default()
525        );
526        println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display());
527        if let Some(rust_module) = &config.config.rust_module {
528            println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module);
529        }
530    }
531    let paths_dependencies =
532        compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
533
534    for path_dependency in paths_dependencies {
535        println!("cargo:rerun-if-changed={}", path_dependency.display());
536    }
537
538    println!("cargo:rerun-if-env-changed=SLINT_STYLE");
539    println!("cargo:rerun-if-env-changed=SLINT_FONT_SIZES");
540    println!("cargo:rerun-if-env-changed=SLINT_SCALE_FACTOR");
541    println!("cargo:rerun-if-env-changed=SLINT_ASSET_SECTION");
542    println!("cargo:rerun-if-env-changed=SLINT_EMBED_RESOURCES");
543    println!("cargo:rerun-if-env-changed=SLINT_EMIT_DEBUG_INFO");
544    println!("cargo:rerun-if-env-changed=SLINT_LIVE_PREVIEW");
545
546    println!(
547        "cargo:rustc-env=SLINT_INCLUDE_GENERATED={}",
548        absolute_rust_output_file_path.display()
549    );
550
551    Ok(())
552}
553
554/// Similar to [`compile_with_config`], but meant to be used independently of cargo.
555///
556/// Will compile the input file and write the result in the given output file.
557///
558/// Both input_slint_file_path and output_rust_file_path should be absolute paths.
559///
560/// Doesn't print any cargo messages.
561///
562/// Returns a list of all input files that were used to generate the output file. (dependencies)
563pub fn compile_with_output_path(
564    input_slint_file_path: impl AsRef<std::path::Path>,
565    output_rust_file_path: impl AsRef<std::path::Path>,
566    config: CompilerConfiguration,
567) -> Result<Vec<std::path::PathBuf>, CompileError> {
568    let mut diag = BuildDiagnostics::default();
569    let syntax_node = i_slint_compiler::parser::parse_file(&input_slint_file_path, &mut diag);
570
571    if diag.has_errors() {
572        let vec = diag.to_string_vec();
573        diag.print();
574        return Err(CompileError::CompileError(vec));
575    }
576
577    let mut compiler_config = config.config;
578    compiler_config.translation_domain = std::env::var("CARGO_PKG_NAME").ok();
579
580    let syntax_node = syntax_node.expect("diags contained no compilation errors");
581
582    // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future
583    let (doc, diag, loader) =
584        spin_on::spin_on(i_slint_compiler::compile_syntax_node(syntax_node, diag, compiler_config));
585
586    if diag.has_errors()
587        || (!diag.is_empty() && std::env::var("SLINT_COMPILER_DENY_WARNINGS").is_ok())
588    {
589        let vec = diag.to_string_vec();
590        diag.print();
591        return Err(CompileError::CompileError(vec));
592    }
593
594    let output_file =
595        std::fs::File::create(&output_rust_file_path).map_err(CompileError::SaveError)?;
596    let mut code_formatter = CodeFormatter::new(BufWriter::new(output_file));
597    let generated = i_slint_compiler::generator::rust::generate(&doc, &loader.compiler_config)
598        .map_err(|e| CompileError::CompileError(vec![e.to_string()]))?;
599
600    let mut dependencies: Vec<std::path::PathBuf> = Vec::new();
601
602    for x in &diag.all_loaded_files {
603        if x.is_absolute() {
604            dependencies.push(x.clone());
605        }
606    }
607
608    // print warnings
609    diag.diagnostics_as_string().lines().for_each(|w| {
610        if !w.is_empty() {
611            println!("cargo:warning={}", w.strip_prefix("warning: ").unwrap_or(w))
612        }
613    });
614
615    write!(code_formatter, "{generated}").map_err(CompileError::SaveError)?;
616    dependencies.push(input_slint_file_path.as_ref().to_path_buf());
617
618    for er in doc.embedded_file_resources.borrow().iter() {
619        if let Some(resource) = er.path.as_deref()
620            && !resource.starts_with("builtin:")
621        {
622            dependencies.push(Path::new(resource).to_path_buf());
623        }
624    }
625
626    code_formatter.sink.flush().map_err(CompileError::SaveError)?;
627
628    Ok(dependencies)
629}
630
631/// This function is for use the application's build script, in order to print any device specific
632/// build flags reported by the backend
633pub fn print_rustc_flags() -> std::io::Result<()> {
634    if let Some(board_config_path) =
635        std::env::var_os("DEP_MCU_BOARD_SUPPORT_BOARD_CONFIG_PATH").map(std::path::PathBuf::from)
636    {
637        let config = std::fs::read_to_string(board_config_path.as_path())?;
638        let toml = config.parse::<toml_edit::DocumentMut>().expect("invalid board config toml");
639
640        for link_arg in
641            toml.get("link_args").and_then(toml_edit::Item::as_array).into_iter().flatten()
642        {
643            if let Some(option) = link_arg.as_str() {
644                println!("cargo:rustc-link-arg={option}");
645            }
646        }
647
648        for link_search_path in
649            toml.get("link_search_path").and_then(toml_edit::Item::as_array).into_iter().flatten()
650        {
651            if let Some(mut path) = link_search_path.as_str().map(std::path::PathBuf::from) {
652                if path.is_relative() {
653                    path = board_config_path.parent().unwrap().join(path);
654                }
655                println!("cargo:rustc-link-search={}", path.to_string_lossy());
656            }
657        }
658        println!("cargo:rerun-if-env-changed=DEP_MCU_BOARD_SUPPORT_MCU_BOARD_CONFIG_PATH");
659        println!("cargo:rerun-if-changed={}", board_config_path.display());
660    }
661
662    Ok(())
663}
664
665#[cfg(test)]
666fn root_path_prefix() -> std::path::PathBuf {
667    #[cfg(windows)]
668    return std::path::PathBuf::from("C:/");
669    #[cfg(not(windows))]
670    return std::path::PathBuf::from("/");
671}
672
673#[test]
674fn with_absolute_library_paths_test() {
675    use std::path::PathBuf;
676
677    let library_paths = std::collections::HashMap::from([
678        ("relative".to_string(), PathBuf::from("some/relative/path")),
679        ("absolute".to_string(), root_path_prefix().join("some/absolute/path")),
680    ]);
681    let config = CompilerConfiguration::new().with_library_paths(library_paths);
682
683    let manifest_path = root_path_prefix().join("path/to/manifest");
684    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
685    let relative = &absolute_config.config.library_paths["relative"];
686    assert!(relative.is_absolute());
687    assert!(relative.starts_with(&manifest_path));
688
689    assert!(!absolute_config.config.library_paths["absolute"].starts_with(&manifest_path));
690}
691
692#[test]
693fn with_absolute_include_paths_test() {
694    use std::path::PathBuf;
695
696    let config = CompilerConfiguration::new().with_include_paths(Vec::from([
697        root_path_prefix().join("some/absolute/path"),
698        PathBuf::from("some/relative/path"),
699    ]));
700
701    let manifest_path = root_path_prefix().join("path/to/manifest");
702    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
703    assert_eq!(
704        absolute_config.config.include_paths,
705        Vec::from([
706            root_path_prefix().join("some/absolute/path"),
707            manifest_path.join("some/relative/path"),
708        ])
709    )
710}