qt-build-utils 0.9.1

Build script helper for linking Qt libraries and using moc code generator. Intended to be used together with cc, cpp_build, or cxx_build
Documentation
// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

use crate::{utils, QmlUri, QtInstallation, QtTool};
use std::{
    path::{Path, PathBuf},
    process::Command,
};

/// Arguments for a [QtToolQmlCacheGen]
#[derive(Clone)]
pub struct QmlCacheArguments {
    /// The URI for the QML module
    pub uri: QmlUri,
    /// The path to the qmldir
    pub qmldir_path: PathBuf,
    /// The path to the qrc file that contains a qmldir
    pub qmldir_qrc_path: PathBuf,
}

/// Paths to files generated by [QtToolQmlCacheGen]
pub struct QmlCacheProducts {
    /// The path of the generated cache file
    pub qml_cache_path: PathBuf,
    /// The Qt resource path for qml file
    pub qml_resource_path: String,
}

/// A wrapper around the [qmlcachegen](https://www.qt.io/blog/qml-type-registration-in-qt-5.15) tool
pub struct QtToolQmlCacheGen {
    executable: PathBuf,
}

impl QtToolQmlCacheGen {
    /// Construct a [QtToolQmlCacheGen] from a given [QtInstallation]
    pub fn new(qt_installation: &dyn QtInstallation) -> Self {
        let executable = qt_installation
            .try_find_tool(QtTool::QmlCacheGen)
            .expect("Could not find qmlcachegen");

        // Ensure that the executable works
        utils::check_executable_help(&executable).unwrap();

        Self { executable }
    }

    /// Run qmlcachegen for a given qml file
    pub fn compile(
        &self,
        common_args: QmlCacheArguments,
        file: impl AsRef<Path>,
    ) -> QmlCacheProducts {
        let uri = common_args.uri;
        let qml_uri_dirs = uri.as_dirs();

        let qmlcachegen_dir = QtTool::QmlCacheGen.writable_path().join(&qml_uri_dirs);
        std::fs::create_dir_all(&qmlcachegen_dir)
            .expect("Could not create qmlcachegen directory for QML module");

        let common_args = [
            "-i".to_owned(),
            common_args.qmldir_path.to_string_lossy().into_owned(),
            "--resource".to_owned(),
            common_args.qmldir_qrc_path.to_string_lossy().into_owned(),
        ];

        let qml_cache_path = qmlcachegen_dir.join(format!(
            "{}.cpp",
            file.as_ref().file_name().unwrap().to_string_lossy()
        ));

        let qml_resource_path = format!("/qt/qml/{qml_uri_dirs}/{}", file.as_ref().display());

        let specific_args = vec![
            "--resource-path".to_owned(),
            qml_resource_path.clone(),
            "-o".to_owned(),
            qml_cache_path.to_string_lossy().into_owned(),
            std::fs::canonicalize(&file)
                .unwrap()
                .to_string_lossy()
                .into_owned(),
        ];

        let cmd = Command::new(&self.executable)
            .args(common_args.iter().chain(&specific_args))
            // Binaries should work without environment and this prevents
            // LD_LIBRARY_PATH from causing different Qt version clashes
            .env_clear()
            .output()
            .unwrap_or_else(|_| {
                panic!(
                    "qmlcachegen failed for {} in QML module {uri}",
                    file.as_ref().display()
                )
            });
        if !cmd.status.success() {
            panic!(
                "qmlcachegen failed for {} in QML module {uri}:\n{}",
                file.as_ref().display(),
                String::from_utf8_lossy(&cmd.stderr)
            );
        }

        QmlCacheProducts {
            qml_cache_path,
            qml_resource_path,
        }
    }

    /// Compile a loader for given qml resource paths
    pub fn compile_loader(
        &self,
        common_args: QmlCacheArguments,
        qml_resource_paths: &[String],
    ) -> PathBuf {
        let uri = common_args.uri;
        let qml_uri_dirs = uri.as_dirs();
        let qml_uri_underscores = uri.as_underscores();

        let qmlcachegen_dir = QtTool::QmlCacheGen.writable_path().join(qml_uri_dirs);
        std::fs::create_dir_all(&qmlcachegen_dir)
            .expect("Could not create qmlcachegen directory for QML module");

        let common_args = [
            "-i".to_owned(),
            common_args.qmldir_path.to_string_lossy().into_owned(),
            "--resource".to_owned(),
            common_args.qmldir_qrc_path.to_string_lossy().into_owned(),
        ];

        let qmlcachegen_loader = qmlcachegen_dir.join("qmlcache_loader.cpp");
        let specific_args = vec![
            "--resource-name".to_owned(),
            format!("qmlcache_{qml_uri_underscores}"),
            "-o".to_owned(),
            qmlcachegen_loader.to_string_lossy().into_owned(),
        ];

        let cmd = Command::new(&self.executable)
            .args(
                common_args
                    .iter()
                    .chain(&specific_args)
                    .chain(qml_resource_paths),
            )
            // Binaries should work without environment and this prevents
            // LD_LIBRARY_PATH from causing different Qt version clashes
            .env_clear()
            .output()
            .unwrap_or_else(|_| panic!("qmlcachegen failed for QML module {uri}"));
        if !cmd.status.success() {
            panic!(
                "qmlcachegen failed for QML module {uri}:\n{}",
                String::from_utf8_lossy(&cmd.stderr)
            );
        }

        qmlcachegen_loader
    }
}