use std::ffi::OsString;
use std::path::Path;
use eyre::bail;
use smol::fs;
use tracing::info;
use crate::{
assets, browser_runtime,
build::{
BuildOptions, BuildProgress, BuiltTarget, RustBuild, RustDynamicLibraries, RustLinkage,
},
device::Artifact,
gtk4::backend::Gtk4Backend,
platform::{PackageOptions, TargetPlatform},
project::Project,
utils::run_command_os,
};
#[cfg(target_os = "linux")]
const GTK4_INIT_HINT: &str = "water run --platform linux --backend gtk4";
#[cfg(not(target_os = "linux"))]
const GTK4_INIT_HINT: &str = "initialize GTK4 backend on Linux";
pub async fn build_gtk4(project: &Project, options: BuildOptions) -> eyre::Result<BuiltTarget> {
ensure_linux_host()?;
let backend_path = project.backend_path::<Gtk4Backend>();
let cargo_toml = backend_path.join("Cargo.toml");
if !cargo_toml.exists() {
bail!(
"GTK4 backend not found at {}. Run `{GTK4_INIT_HINT}` to initialize it.",
backend_path.display(),
);
}
let mut build = RustBuild::new(&backend_path, TargetPlatform::Linux.triple())
.with_project(project)
.with_target_dir(project.water_target_dir(options.linkage()).await?)
.with_linkage(
options.linkage(),
&format!("{}/dev", project.crate_name()),
&["$ORIGIN"],
)
.with_envs(options.cargo_envs().iter().cloned());
if let Some(sccache_path) = options.sccache_path() {
build = build.with_sccache(sccache_path.to_path_buf());
}
if let Some(progress) = options.progress() {
build = build.with_progress(progress.clone());
}
let built_target = build
.build_binary(
project.gtk_backend_crate_name().as_str(),
options.is_release(),
)
.await
.map_err(|error| eyre::eyre!("Failed to build GTK4 backend with cargo: {error}"))?;
Ok(built_target)
}
pub async fn clean_gtk4(project: &Project) -> eyre::Result<()> {
ensure_linux_host()?;
let backend_path = project.backend_path::<Gtk4Backend>();
let cargo_toml = backend_path.join("Cargo.toml");
if !cargo_toml.exists() {
return Ok(()); }
for linkage in [RustLinkage::SharedRuntime, RustLinkage::Static] {
let backend_target_dir = project.water_target_dir(linkage).await?;
if !backend_target_dir.exists() {
continue;
}
let args: Vec<OsString> = vec![
"clean".into(),
"--manifest-path".into(),
cargo_toml.as_os_str().to_owned(),
"--target-dir".into(),
backend_target_dir.as_os_str().to_owned(),
"--package".into(),
project.gtk_backend_crate_name().as_str().into(),
];
run_command_os("cargo", args).await?;
}
Ok(())
}
pub async fn package_gtk4(
project: &Project,
options: PackageOptions,
built: &BuiltTarget,
) -> eyre::Result<Artifact> {
ensure_linux_host()?;
let backend_path = project.backend_path::<Gtk4Backend>();
copy_assets_and_fonts(
project,
&backend_path,
None,
options.uses_dev_server(),
options.progress(),
)
.await?;
let target_dir = &built.profile_dir;
let profile = if options.is_debug() {
"debug"
} else {
"release"
};
let final_binary_path = &built.artifact;
let runtime_plan = project
.browser_runtime_plan(TargetPlatform::Linux, crate::platform::TargetBackend::Gtk4)
.await?;
let runtime_dir =
crate::platforming::packaging::dist_dir(&backend_path, "linux", Some(profile));
fs::create_dir_all(&runtime_dir).await?;
browser_runtime::stage(
runtime_plan,
TargetPlatform::Linux,
target_dir,
&runtime_dir,
)
.await?;
if options.uses_shared_rust_runtime() {
RustDynamicLibraries::resolve(built, &TargetPlatform::Linux.triple(), project)
.await?
.stage(&runtime_dir)
.await?;
} else {
RustDynamicLibraries::remove_staged(&runtime_dir, &TargetPlatform::Linux.triple()).await?;
}
let packaged_binary = crate::platforming::packaging::stage_binary_as(
final_binary_path,
&runtime_dir,
project.gtk4_binary_name().as_str(),
)
.await?;
Ok(Artifact::new(project.bundle_identifier(), packaged_binary))
}
#[must_use]
pub const fn is_gtk4_platform(platform: TargetPlatform) -> bool {
matches!(platform, TargetPlatform::Linux)
}
fn ensure_linux_host() -> eyre::Result<()> {
if cfg!(target_os = "linux") {
Ok(())
} else {
bail!("GTK4 backend is only supported on Linux hosts");
}
}
async fn copy_assets_and_fonts(
project: &Project,
backend_path: &Path,
sccache_path: Option<&Path>,
dev_server: bool,
progress: Option<&BuildProgress>,
) -> eyre::Result<()> {
let resources_dir = backend_path.join("resources");
fs::create_dir_all(&resources_dir).await?;
let manifest = assets::stage_project_assets_for_gtk(
project,
&resources_dir,
sccache_path,
dev_server,
progress,
)
.await?;
assets::stage_hicolor_icons(project, &resources_dir.join("icons")).await?;
let font_declarations = assets::scan_fonts(project, &backend_path.join("Cargo.toml")).await?;
let mut resolved_fonts = assets::resolve_fonts(font_declarations).await?;
resolved_fonts.extend(assets::scan_project_font_assets(&manifest)?);
if !resolved_fonts.is_empty() {
let fonts_dest = resources_dir.join("fonts");
assets::copy_fonts(&resolved_fonts, &fonts_dest).await?;
info!("Copied {} fonts to GTK4 resources", resolved_fonts.len());
}
Ok(())
}