Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
test:
name: test (${{ matrix.target }}, ${{ matrix.link }})
strategy:
fail-fast: false
matrix:
target:
- x86_64-pc-windows-msvc
- aarch64-pc-windows-msvc
- i686-pc-windows-msvc
- aarch64-apple-darwin
- x86_64-apple-darwin
- x86_64-unknown-linux-gnu
- aarch64-unknown-linux-gnu
link: [shared, static]
include:
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-11-arm
- target: i686-pc-windows-msvc
os: windows-latest
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-15-intel
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest
- target: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}

# prism asks for C23, which older CMake cannot map to an MSVC flag.
- uses: lukka/get-cmake@latest

- name: Install what the Linux backends build against
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libspeechd-dev libglibmm-2.68-dev

- name: Test
shell: bash
# Skips doctests, since rustdoc links its own binary without the
# delay-load flags or the library search path cargo sets.
run: cargo test --tests --target ${{ matrix.target }} ${{ matrix.link == 'static' && '--features static' || '' }}
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/prism-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ workspace = true

[build-dependencies]
cmake = "0.1.58"
pkg-config = "0.3"

[features]
static = []
39 changes: 39 additions & 0 deletions crates/prism-sys/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use cmake::Config;
/// otherwise it failed with unresolved UI Automation, RPC and Windows Runtime symbols.
const WINDOWS_SYSTEM_LIBS: &[&str] = &["ole32", "onecore", "runtimeobject", "uiautomationcore", "rpcrt4", "powrprof"];

/// The frameworks prism links itself on macOS, in `PrismPlatformApple.cmake`.
const MACOS_FRAMEWORKS: &[&str] = &["Foundation", "AVFoundation", "AppKit", "IOKit", "CoreFoundation"];

fn main() {
println!("cargo:rerun-if-env-changed=PRISM_LIB_DIR");
let static_link = env::var("CARGO_FEATURE_STATIC").is_ok();
Expand All @@ -24,6 +27,42 @@ fn main() {
if static_link {
link_cpp_runtime();
link_windows_import_libs();
link_macos_frameworks();
link_pkg_config_modules();
}
}

fn link_macos_frameworks() {
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
return;
}
for framework in MACOS_FRAMEWORKS {
println!("cargo:rustc-link-lib=framework={framework}");
}
}

/// Links the pkg-config modules the Linux backends were built against.
///
/// Which ones depends on what the build found, so the list comes from the
/// `prism-config.cmake` prism installs for its own static consumers.
fn link_pkg_config_modules() {
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("linux") {
return;
}
let Some(config) = env::var_os("OUT_DIR").map(PathBuf::from).map(|out| out.join("share/prism/prism-config.cmake"))
else {
return;
};
let Ok(text) = std::fs::read_to_string(&config) else {
println!("cargo:warning=prism-config.cmake is missing; the Linux backends will not link");
return;
};
for line in text.lines().filter(|line| line.trim_start().starts_with("pkg_check_modules(")) {
let Some(module) = line.split('"').nth(1) else { continue };
let (name, version) = module.split_once(">=").unwrap_or((module, "0"));
if let Err(error) = pkg_config::Config::new().atleast_version(version).probe(name) {
println!("cargo:warning={error}");
}
}
}

Expand Down
7 changes: 6 additions & 1 deletion crates/prismer/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@
//!
//! Cargo hands a `links` crate's metadata to its direct dependents only, so
//! without this an application depending on prismer could not see what
//! prism-sys published.
//! prism-sys published. prismer's own tests and examples are applications
//! too, and get the same flags an application's build script would set.

fn main() {
println!("cargo:rerun-if-changed=build.rs");
if let Ok(dlls) = std::env::var("DEP_PRISM_DELAY_LOAD_DLLS") {
println!("cargo:delay_load_dlls={dlls}");
for dll in dlls.split(';').filter(|dll| !dll.is_empty()) {
println!("cargo:rustc-link-arg-tests=/DELAYLOAD:{dll}");
println!("cargo:rustc-link-arg-examples=/DELAYLOAD:{dll}");
}
}
}
29 changes: 29 additions & 0 deletions crates/prismer/tests/platform.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! Checks that prism loads and registers the backends this platform should
//! have, which catches a static link that dropped them.

use prismer::Prism;

#[cfg(windows)]
const EXPECTED: &[&str] = &["SAPI", "NVDA", "JAWS", "OneCore", "ZoomText"];
#[cfg(target_os = "macos")]
const EXPECTED: &[&str] = &["AVSpeech", "VoiceOver"];
#[cfg(target_os = "linux")]
const EXPECTED: &[&str] = &["Speech Dispatcher", "Orca"];

#[test]
fn registers_the_platform_backends() {
let prism = Prism::new().expect("prism starts");
let names: Vec<String> = prism.backend_ids().into_iter().filter_map(|id| prism.backend_name(id)).collect();
for expected in EXPECTED {
assert!(names.iter().any(|name| name == expected), "{expected} is not registered, only {names:?}");
}
}

/// Only these have a voice on a machine with nothing installed.
#[cfg(any(windows, target_os = "macos"))]
#[test]
fn speaks_through_the_best_backend() {
let prism = Prism::new().expect("prism starts");
let backend = prism.create_best().expect("a backend starts");
backend.speak("Hello from prismer.", true).expect("speech starts");
}
Loading