fix(desktop): build with compatible PipeWire headers (#2043)

This commit is contained in:
Hampus
2026-08-27 15:18:15 +02:00
committed by GitHub
parent 23493b4ac2
commit fc3065ebe4
+239 -19
View File
@@ -33,6 +33,9 @@ const PUBLIC_DL_BASE: &str = "https://api.fluxer.app/dl";
const PNPM_VERSION: &str = "10.29.3";
const RUST_TOOLCHAIN: &str = "1.93.0";
const DEFAULT_DESKTOP_VARIANT: &str = "default";
const LINUX_PIPEWIRE_VERSION: &str = "0.3.65";
const LINUX_PIPEWIRE_SOURCE_SHA256: &str =
"bb76f938136d0ce8c35bffa99e002dc2dbaeab5e14c6c34154e7f750013d1d6b";
const LINUX_LIBFIDO2_VERSION: &str = "1.16.0";
const LINUX_LIBFIDO2_SOURCE_SHA256: &str =
"8c2b6fb279b5b42e9ac92ade71832e485852647b53607c43baaafbbcecea04e4";
@@ -812,6 +815,8 @@ DPkg::Lock::Timeout "120";
"build-essential",
"binutils",
"cmake",
"meson",
"ninja-build",
"nasm",
"rpm",
"desktop-file-utils",
@@ -836,10 +841,217 @@ DPkg::Lock::Timeout "120";
"clang",
"libpulse-dev",
])?;
install_linux_pipewire_headers().await?;
install_linux_libfido2().await?;
run_command(CommandSpec::new("sudo").args(["gem", "install", "--no-document", "fpm"]))
}
async fn install_linux_pipewire_headers() -> Result<()> {
let temp = TempDir::new().context("Failed to create PipeWire header build directory")?;
let archive_name = format!("pipewire-{LINUX_PIPEWIRE_VERSION}.tar.gz");
let archive_path = temp.path().join(&archive_name);
let source_dir = temp.path().join("source");
let build_dir = temp.path().join("build");
let staging_dir = temp.path().join("stage");
let source_url = format!(
"https://gitlab.freedesktop.org/pipewire/pipewire/-/archive/{LINUX_PIPEWIRE_VERSION}/{archive_name}"
);
let multiarch = linux_multiarch()?;
let install_library_dir = Path::new("/usr/local/lib").join(&multiarch);
let install_pkgconfig_dir = install_library_dir.join("pkgconfig");
let libdir_arg = format!("--libdir=lib/{multiarch}");
let system_pipewire_library = fs::canonicalize(linux_loader_path("libpipewire-0.3.so.0")?)
.context("Failed to resolve the system PipeWire library")?;
ensure!(
!system_pipewire_library.starts_with("/usr/local"),
"Expected the system PipeWire runtime, got {}",
system_pipewire_library.display()
);
download_file(&source_url, &archive_path).await?;
let archive_sha256 = sha256_file(&archive_path)?;
ensure!(
archive_sha256 == LINUX_PIPEWIRE_SOURCE_SHA256,
"PipeWire source checksum mismatch: expected {}, got {}",
LINUX_PIPEWIRE_SOURCE_SHA256,
archive_sha256
);
fs::create_dir_all(&source_dir)
.with_context(|| format!("Failed to create {}", source_dir.display()))?;
run_command(CommandSpec::new("tar").args([
"-xzf",
archive_path.to_string_lossy().as_ref(),
"-C",
source_dir.to_string_lossy().as_ref(),
"--strip-components=1",
]))?;
run_command(CommandSpec::new("meson").args([
"setup",
build_dir.to_string_lossy().as_ref(),
source_dir.to_string_lossy().as_ref(),
"--buildtype=release",
"--default-library=shared",
"--prefix=/usr/local",
&libdir_arg,
"--auto-features=disabled",
"-Dexamples=disabled",
"-Dtests=disabled",
"-Dinstalled_tests=disabled",
"-Ddocs=disabled",
"-Dman=disabled",
"-Dspa-plugins=enabled",
"-Dpipewire-alsa=disabled",
"-Dpipewire-jack=disabled",
"-Dpipewire-v4l2=disabled",
"-Dsystemd=disabled",
"-Ddbus=disabled",
"-Dflatpak=disabled",
"-Dsession-managers=[]",
"-Dlegacy-rtkit=false",
]))?;
run_command(CommandSpec::new("meson").args([
"compile",
"-C",
build_dir.to_string_lossy().as_ref(),
]))?;
run_command(
CommandSpec::new("meson")
.args([
"install",
"-C",
build_dir.to_string_lossy().as_ref(),
"--no-rebuild",
])
.env("DESTDIR", staging_dir.as_os_str()),
)?;
let staged_prefix = staging_dir.join("usr/local");
let staged_pipewire_headers = staged_prefix.join("include/pipewire-0.3");
let staged_spa_headers = staged_prefix.join("include/spa-0.2");
let staged_pkgconfig_dir = staged_prefix.join(format!("lib/{multiarch}/pkgconfig"));
let install_include_dir = Path::new("/usr/local/include");
let install_pipewire_headers = install_include_dir.join("pipewire-0.3");
let install_spa_headers = install_include_dir.join("spa-0.2");
ensure!(
!install_pipewire_headers.exists() && !install_spa_headers.exists(),
"PipeWire header overlay already exists under {}",
install_include_dir.display()
);
run_command(CommandSpec::new("sudo").args([
"install",
"-d",
install_include_dir.to_string_lossy().as_ref(),
install_pkgconfig_dir.to_string_lossy().as_ref(),
]))?;
run_command(CommandSpec::new("sudo").args([
"cp",
"-a",
staged_pipewire_headers.to_string_lossy().as_ref(),
staged_spa_headers.to_string_lossy().as_ref(),
install_include_dir.to_string_lossy().as_ref(),
]))?;
for pkgconfig_name in ["libpipewire-0.3.pc", "libspa-0.2.pc"] {
run_command(
CommandSpec::new("sudo").args([
"install",
"-m",
"0644",
staged_pkgconfig_dir
.join(pkgconfig_name)
.to_string_lossy()
.as_ref(),
install_pkgconfig_dir
.join(pkgconfig_name)
.to_string_lossy()
.as_ref(),
]),
)?;
}
let installed_version =
output_text(CommandSpec::new("pkg-config").args(["--modversion", "libpipewire-0.3"]))?;
ensure!(
installed_version == LINUX_PIPEWIRE_VERSION,
"Expected PipeWire headers {}, got {}",
LINUX_PIPEWIRE_VERSION,
installed_version
);
for (package, expected_include_flag) in [
("libpipewire-0.3", "-I/usr/local/include/pipewire-0.3"),
("libspa-0.2", "-I/usr/local/include/spa-0.2"),
] {
let pkgconfig_dir =
output_text(CommandSpec::new("pkg-config").args(["--variable=pcfiledir", package]))?;
ensure!(
Path::new(&pkgconfig_dir) == install_pkgconfig_dir,
"Expected {package} metadata in {}, got {}",
install_pkgconfig_dir.display(),
pkgconfig_dir
);
let include_dir =
output_text(CommandSpec::new("pkg-config").args(["--variable=includedir", package]))?;
ensure!(
Path::new(&include_dir) == install_include_dir,
"Expected {package} headers in {}, got {}",
install_include_dir.display(),
include_dir
);
let include_flags =
output_text(CommandSpec::new("pkg-config").args(["--cflags-only-I", package]))?;
ensure!(
include_flags
.split_whitespace()
.any(|flag| flag == expected_include_flag),
"Expected {package} include flag {expected_include_flag}, got {include_flags}"
);
}
ensure!(
install_pipewire_headers
.join("pipewire/pipewire.h")
.is_file()
&& install_spa_headers.join("spa/buffer/meta.h").is_file()
&& install_spa_headers.join("spa/param/video/raw.h").is_file(),
"PipeWire {} header overlay is incomplete",
LINUX_PIPEWIRE_VERSION
);
let mut local_pipewire_library = None;
for entry in fs::read_dir(&install_library_dir)
.with_context(|| format!("Failed to read {}", install_library_dir.display()))?
{
let entry = entry
.with_context(|| format!("Failed to inspect {}", install_library_dir.display()))?;
if entry
.file_name()
.to_string_lossy()
.starts_with("libpipewire-0.3.")
{
local_pipewire_library = Some(entry.path());
break;
}
}
if let Some(path) = local_pipewire_library {
bail!(
"PipeWire header overlay found unexpected library {}",
path.display()
);
}
let loaded_pipewire_library = fs::canonicalize(linux_loader_path("libpipewire-0.3.so.0")?)
.context("Failed to resolve the selected PipeWire library")?;
ensure!(
loaded_pipewire_library == system_pipewire_library,
"PipeWire header overlay changed the runtime from {} to {}",
system_pipewire_library.display(),
loaded_pipewire_library.display()
);
println!(
"Installed PipeWire {LINUX_PIPEWIRE_VERSION} headers for {}.",
system_pipewire_library.display()
);
Ok(())
}
async fn install_linux_libfido2() -> Result<()> {
let temp = TempDir::new().context("Failed to create libfido2 build directory")?;
let archive_name = format!("libfido2-{LINUX_LIBFIDO2_VERSION}.tar.gz");
@@ -847,16 +1059,8 @@ async fn install_linux_libfido2() -> Result<()> {
let source_dir = temp.path().join("source");
let build_dir = temp.path().join("build");
let source_url = format!("https://developers.yubico.com/libfido2/Releases/{archive_name}");
let multiarch = output_text(CommandSpec::new("gcc").arg("-print-multiarch"))?;
let multiarch = multiarch.trim();
ensure!(
!multiarch.is_empty()
&& multiarch
.bytes()
.all(|byte| { byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_' }),
"gcc returned invalid multiarch tuple: {multiarch:?}"
);
let install_library_dir = Path::new("/usr/local/lib").join(multiarch);
let multiarch = linux_multiarch()?;
let install_library_dir = Path::new("/usr/local/lib").join(&multiarch);
let install_library_arg = format!("-DCMAKE_INSTALL_LIBDIR=lib/{multiarch}");
download_file(&source_url, &archive_path).await?;
@@ -945,15 +1149,7 @@ async fn install_linux_libfido2() -> Result<()> {
);
let installed_library = fs::canonicalize(&installed_soname)
.with_context(|| format!("Failed to resolve {}", installed_soname.display()))?;
let loader_cache = output_text(CommandSpec::new("ldconfig").arg("-p"))?;
let loader_path = loader_cache
.lines()
.find_map(|line| {
let (library, path) = line.trim().split_once("=>")?;
(library.split_whitespace().next() == Some("libfido2.so.1"))
.then(|| PathBuf::from(path.trim()))
})
.context("ldconfig did not resolve libfido2.so.1")?;
let loader_path = linux_loader_path("libfido2.so.1")?;
let loaded_library = fs::canonicalize(&loader_path)
.with_context(|| format!("Failed to resolve {}", loader_path.display()))?;
ensure!(
@@ -967,6 +1163,30 @@ async fn install_linux_libfido2() -> Result<()> {
Ok(())
}
fn linux_multiarch() -> Result<String> {
let multiarch = output_text(CommandSpec::new("gcc").arg("-print-multiarch"))?;
ensure!(
!multiarch.is_empty()
&& multiarch
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_'),
"gcc returned invalid multiarch tuple: {multiarch:?}"
);
Ok(multiarch)
}
fn linux_loader_path(library_name: &str) -> Result<PathBuf> {
let loader_cache = output_text(CommandSpec::new("ldconfig").arg("-p"))?;
loader_cache
.lines()
.find_map(|line| {
let (library, path) = line.trim().split_once("=>")?;
(library.split_whitespace().next() == Some(library_name))
.then(|| PathBuf::from(path.trim()))
})
.with_context(|| format!("ldconfig did not resolve {library_name}"))
}
fn rewrite_ubuntu_ports_sources() -> Result<()> {
let apt = Path::new("/etc/apt");
if !apt.exists() {